Commit 266de1b2 by michaelkatz Committed by Marc Baechinger

Adjust track selection with Dolby Vision if display does not support

If the sample type is Dolby Vision and the display does not support Dolby Vision, then the capabilities DecoderSupport flag is set to DECODER_SUPPORT_FALLBACK_MIMETYPE. This denotes that the renderer will use a decoder for a fallback mimetype if possible. This alters track selection as tracks with DecoderSupport DECODER_SUPPORT_PRIMARY are preferred.

UnitTests included
-DefaultTrackSelector test that checks track selection reordering with DECODER_SUPPORT_FALLBACK_MIMETYPE
-MediaCodecVideoRenderer test that checks setting of DecoderSupport flag based on Display's Dolby Vision support

Issue: google/ExoPlayer#8944
PiperOrigin-RevId: 480040876
parent ba1ecafb
......@@ -19,6 +19,8 @@
* Add suppression reason for unsuitable audio route and play when ready
change reason for suppressed too long.
([#15](https://github.com/androidx/media/issues/15)).
* Prefer other tracks to Dolby Vision if display does not support it.
([#8944](https://github.com/google/ExoPlayer/issues/8944)).
* Downloads:
* Fix potential infinite loop in `ProgressiveDownloader` caused by
simultaneous download and playback with the same `PriorityTaskManager`
......
......@@ -131,21 +131,23 @@ public interface RendererCapabilities {
int HARDWARE_ACCELERATION_NOT_SUPPORTED = 0;
/**
* Level of decoder support. One of {@link #DECODER_SUPPORT_PRIMARY} and {@link
* #DECODER_SUPPORT_FALLBACK}.
* Level of decoder support. One of {@link #DECODER_SUPPORT_FALLBACK_MIMETYPE}, {@link
* #DECODER_SUPPORT_FALLBACK}, and {@link #DECODER_SUPPORT_PRIMARY}.
*
* <p>For video renderers, the level of support is indicated for non-tunneled output.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
DECODER_SUPPORT_PRIMARY,
DECODER_SUPPORT_FALLBACK,
})
@IntDef({DECODER_SUPPORT_FALLBACK_MIMETYPE, DECODER_SUPPORT_PRIMARY, DECODER_SUPPORT_FALLBACK})
@interface DecoderSupport {}
/** A mask to apply to {@link Capabilities} to obtain {@link DecoderSupport} only. */
int MODE_SUPPORT_MASK = 0b1 << 7;
int MODE_SUPPORT_MASK = 0b11 << 7;
/**
* The renderer will use a decoder for fallback mimetype if possible as format's MIME type is
* unsupported
*/
int DECODER_SUPPORT_FALLBACK_MIMETYPE = 0b10 << 7;
/** The renderer is able to use the primary decoder for the format's MIME type. */
int DECODER_SUPPORT_PRIMARY = 0b1 << 7;
/** The renderer will use a fallback decoder. */
......
......@@ -3107,6 +3107,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return 0;
}
switch (mimeType) {
case MimeTypes.VIDEO_DOLBY_VISION:
return 5;
case MimeTypes.VIDEO_AV1:
return 4;
case MimeTypes.VIDEO_H265:
......
......@@ -40,6 +40,7 @@ import android.util.Pair;
import android.view.Display;
import android.view.Surface;
import androidx.annotation.CallSuper;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C;
......@@ -412,6 +413,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@DecoderSupport
int decoderSupport = isPreferredDecoder ? DECODER_SUPPORT_PRIMARY : DECODER_SUPPORT_FALLBACK;
if (Util.SDK_INT >= 26
&& MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType)
&& !Api26.doesDisplaySupportDolbyVision(context)) {
decoderSupport = DECODER_SUPPORT_FALLBACK_MIMETYPE;
}
@TunnelingSupport int tunnelingSupport = TUNNELING_NOT_SUPPORTED;
if (isFormatSupported) {
List<MediaCodecInfo> tunnelingDecoderInfos =
......@@ -488,8 +495,20 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
alternativeMimeType, requiresSecureDecoder, requiresTunnelingDecoder);
if (Util.SDK_INT >= 26
&& MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType)
&& !alternativeDecoderInfos.isEmpty()) {
// If sample type is Dolby Vision, check if Display supports Dolby Vision
&& !alternativeDecoderInfos.isEmpty()
&& !Api26.doesDisplaySupportDolbyVision(context)) {
return ImmutableList.copyOf(alternativeDecoderInfos);
}
return ImmutableList.<MediaCodecInfo>builder()
.addAll(decoderInfos)
.addAll(alternativeDecoderInfos)
.build();
}
@RequiresApi(26)
private static final class Api26 {
@DoNotInline
public static boolean doesDisplaySupportDolbyVision(Context context) {
boolean supportsDolbyVision = false;
DisplayManager displayManager =
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
......@@ -504,14 +523,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
}
}
if (!supportsDolbyVision) {
return ImmutableList.copyOf(alternativeDecoderInfos);
}
return supportsDolbyVision;
}
return ImmutableList.<MediaCodecInfo>builder()
.addAll(decoderInfos)
.addAll(alternativeDecoderInfos)
.build();
}
@Override
......
......@@ -21,6 +21,7 @@ import static androidx.media3.common.C.FORMAT_UNSUPPORTED_SUBTYPE;
import static androidx.media3.common.C.FORMAT_UNSUPPORTED_TYPE;
import static androidx.media3.exoplayer.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS;
import static androidx.media3.exoplayer.RendererCapabilities.DECODER_SUPPORT_FALLBACK;
import static androidx.media3.exoplayer.RendererCapabilities.DECODER_SUPPORT_FALLBACK_MIMETYPE;
import static androidx.media3.exoplayer.RendererCapabilities.DECODER_SUPPORT_PRIMARY;
import static androidx.media3.exoplayer.RendererCapabilities.HARDWARE_ACCELERATION_NOT_SUPPORTED;
import static androidx.media3.exoplayer.RendererCapabilities.HARDWARE_ACCELERATION_SUPPORTED;
......@@ -2245,6 +2246,68 @@ public final class DefaultTrackSelectorTest {
}
/**
* Tests that track selector will select video track with support of its primary decoder over a
* track that will use a decoder for it's format fallback sampleMimetype.
*/
@Test
public void selectTracks_withDecoderSupportFallbackMimetype_selectsTrackWithPrimaryDecoder()
throws Exception {
Format formatDV =
new Format.Builder().setId("0").setSampleMimeType(MimeTypes.VIDEO_DOLBY_VISION).build();
Format formatHevc =
new Format.Builder().setId("1").setSampleMimeType(MimeTypes.VIDEO_H265).build();
TrackGroupArray trackGroups =
new TrackGroupArray(new TrackGroup(formatDV), new TrackGroup(formatHevc));
@Capabilities
int capabilitiesDecoderSupportPrimary =
RendererCapabilities.create(
FORMAT_HANDLED,
ADAPTIVE_NOT_SEAMLESS,
TUNNELING_NOT_SUPPORTED,
HARDWARE_ACCELERATION_SUPPORTED,
DECODER_SUPPORT_PRIMARY);
int capabilitiesDecoderSupportFallbackType =
RendererCapabilities.create(
FORMAT_HANDLED,
ADAPTIVE_NOT_SEAMLESS,
TUNNELING_NOT_SUPPORTED,
HARDWARE_ACCELERATION_SUPPORTED,
DECODER_SUPPORT_FALLBACK_MIMETYPE);
// Select track supported by primary decoder by default.
ImmutableMap<String, Integer> rendererCapabilitiesMapDifferingDecoderSupport =
ImmutableMap.of(
"0", capabilitiesDecoderSupportFallbackType, "1", capabilitiesDecoderSupportPrimary);
RendererCapabilities rendererCapabilitiesDifferingDecoderSupport =
new FakeMappedRendererCapabilities(
C.TRACK_TYPE_VIDEO, rendererCapabilitiesMapDifferingDecoderSupport);
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {rendererCapabilitiesDifferingDecoderSupport},
trackGroups,
periodId,
TIMELINE);
assertFixedSelection(result.selections[0], trackGroups, formatHevc);
// Select Dolby Vision track over HEVC when renderer supports both equally
ImmutableMap<String, Integer> rendererCapabilitiesMapAllPrimaryDecoderSupport =
ImmutableMap.of(
"0", capabilitiesDecoderSupportPrimary, "1", capabilitiesDecoderSupportPrimary);
RendererCapabilities rendererCapabilitiesAllPrimaryDecoderSupport =
new FakeMappedRendererCapabilities(
C.TRACK_TYPE_VIDEO, rendererCapabilitiesMapAllPrimaryDecoderSupport);
result =
trackSelector.selectTracks(
new RendererCapabilities[] {rendererCapabilitiesAllPrimaryDecoderSupport},
trackGroups,
periodId,
TIMELINE);
assertFixedSelection(result.selections[0], trackGroups, formatDV);
}
/**
* Tests that track selector will select the video track with the highest number of matching role
* flags given by {@link Parameters}.
*/
......
......@@ -15,6 +15,7 @@
*/
package androidx.media3.exoplayer.video;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.format;
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
......@@ -26,13 +27,16 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.display.DisplayManager;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.view.Display;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
......@@ -64,6 +68,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.Shadows;
import org.robolectric.shadows.ShadowDisplay;
import org.robolectric.shadows.ShadowLooper;
/** Unit test for {@link MediaCodecVideoRenderer}. */
......@@ -612,6 +618,100 @@ public class MediaCodecVideoRendererTest {
}
@Test
public void supportsFormat_withDolbyVision_setsDecoderSupportFlagsByDisplayDolbyVisionSupport()
throws Exception {
Format formatDvheDtr =
new Format.Builder()
.setSampleMimeType(MimeTypes.VIDEO_DOLBY_VISION)
.setCodecs("dvhe.04.01")
.build();
// Provide supporting Dolby Vision and fallback HEVC decoders
MediaCodecSelector mediaCodecSelector =
(mimeType, requiresSecureDecoder, requiresTunnelingDecoder) -> {
switch (mimeType) {
case MimeTypes.VIDEO_DOLBY_VISION:
{
CodecCapabilities capabilitiesDolby = new CodecCapabilities();
capabilitiesDolby.profileLevels = new CodecProfileLevel[] {new CodecProfileLevel()};
capabilitiesDolby.profileLevels[0].profile =
CodecProfileLevel.DolbyVisionProfileDvheDtr;
capabilitiesDolby.profileLevels[0].level = CodecProfileLevel.DolbyVisionLevelFhd30;
return ImmutableList.of(
MediaCodecInfo.newInstance(
/* name= */ "dvhe-codec",
/* mimeType= */ mimeType,
/* codecMimeType= */ mimeType,
/* capabilities= */ capabilitiesDolby,
/* hardwareAccelerated= */ true,
/* softwareOnly= */ false,
/* vendor= */ false,
/* forceDisableAdaptive= */ false,
/* forceSecure= */ false));
}
case MimeTypes.VIDEO_H265:
{
CodecCapabilities capabilitiesH265 = new CodecCapabilities();
capabilitiesH265.profileLevels =
new CodecProfileLevel[] {new CodecProfileLevel(), new CodecProfileLevel()};
capabilitiesH265.profileLevels[0].profile = CodecProfileLevel.HEVCProfileMain;
capabilitiesH265.profileLevels[0].level = CodecProfileLevel.HEVCMainTierLevel41;
capabilitiesH265.profileLevels[1].profile = CodecProfileLevel.HEVCProfileMain10;
capabilitiesH265.profileLevels[1].level = CodecProfileLevel.HEVCHighTierLevel51;
return ImmutableList.of(
MediaCodecInfo.newInstance(
/* name= */ "h265-codec",
/* mimeType= */ mimeType,
/* codecMimeType= */ mimeType,
/* capabilities= */ capabilitiesH265,
/* hardwareAccelerated= */ true,
/* softwareOnly= */ false,
/* vendor= */ false,
/* forceDisableAdaptive= */ false,
/* forceSecure= */ false));
}
default:
return ImmutableList.of();
}
};
MediaCodecVideoRenderer renderer =
new MediaCodecVideoRenderer(
ApplicationProvider.getApplicationContext(),
mediaCodecSelector,
/* allowedJoiningTimeMs= */ 0,
/* eventHandler= */ new Handler(testMainLooper),
/* eventListener= */ eventListener,
/* maxDroppedFramesToNotify= */ 1);
renderer.init(/* index= */ 0, PlayerId.UNSET);
@Capabilities int capabilitiesDvheDtr = renderer.supportsFormat(formatDvheDtr);
assertThat(RendererCapabilities.getDecoderSupport(capabilitiesDvheDtr))
.isEqualTo(RendererCapabilities.DECODER_SUPPORT_FALLBACK_MIMETYPE);
// Set Display to have Dolby Vision support
Context context = ApplicationProvider.getApplicationContext();
DisplayManager displayManager =
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
Display display = (displayManager != null) ? displayManager.getDisplay(DEFAULT_DISPLAY) : null;
ShadowDisplay shadowDisplay = Shadows.shadowOf(display);
int[] hdrCapabilities =
new int[] {
Display.HdrCapabilities.HDR_TYPE_HDR10, Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION
};
shadowDisplay.setDisplayHdrCapabilities(
display.getDisplayId(),
/* maxLuminance= */ 100f,
/* maxAverageLuminance= */ 100f,
/* minLuminance= */ 100f,
hdrCapabilities);
capabilitiesDvheDtr = renderer.supportsFormat(formatDvheDtr);
assertThat(RendererCapabilities.getDecoderSupport(capabilitiesDvheDtr))
.isEqualTo(RendererCapabilities.DECODER_SUPPORT_PRIMARY);
}
@Test
public void getCodecMaxInputSize_videoH263() {
MediaCodecInfo codecInfo = createMediaCodecInfo(MimeTypes.VIDEO_H263);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment