Skip to content

Commit

Permalink
Adjust track selection with Dolby Vision if display does not support
Browse files Browse the repository at this point in the history
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#8944
PiperOrigin-RevId: 480040876
  • Loading branch information
microkatz authored and marcbaechinger committed Oct 20, 2022
1 parent 2188685 commit a366590
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,21 +127,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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3100,6 +3100,8 @@ private static int getVideoCodecPreferenceScore(@Nullable String mimeType) {
return 0;
}
switch (mimeType) {
case MimeTypes.VIDEO_DOLBY_VISION:
return 5;
case MimeTypes.VIDEO_AV1:
return 4;
case MimeTypes.VIDEO_H265:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
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 com.google.android.exoplayer2.C;
Expand Down Expand Up @@ -409,6 +410,12 @@ public String getName() {
@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 =
Expand Down Expand Up @@ -485,8 +492,20 @@ private static List<MediaCodecInfo> getDecoderInfos(
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);
Expand All @@ -501,14 +520,8 @@ private static List<MediaCodecInfo> getDecoderInfos(
}
}
}
if (!supportsDolbyVision) {
return ImmutableList.copyOf(alternativeDecoderInfos);
}
return supportsDolbyVision;
}
return ImmutableList.<MediaCodecInfo>builder()
.addAll(decoderInfos)
.addAll(alternativeDecoderInfos)
.build();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static com.google.android.exoplayer2.C.FORMAT_UNSUPPORTED_TYPE;
import static com.google.android.exoplayer2.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS;
import static com.google.android.exoplayer2.RendererCapabilities.DECODER_SUPPORT_FALLBACK;
import static com.google.android.exoplayer2.RendererCapabilities.DECODER_SUPPORT_FALLBACK_MIMETYPE;
import static com.google.android.exoplayer2.RendererCapabilities.DECODER_SUPPORT_PRIMARY;
import static com.google.android.exoplayer2.RendererCapabilities.HARDWARE_ACCELERATION_NOT_SUPPORTED;
import static com.google.android.exoplayer2.RendererCapabilities.HARDWARE_ACCELERATION_SUPPORTED;
Expand Down Expand Up @@ -2242,6 +2243,68 @@ public void selectTracks_withPreferredVideoMimeTypes_selectsTrackWithPreferredMi
assertAdaptiveSelection(result.selections[0], adaptiveGroup, /* expectedTracks...= */ 1, 0);
}

/**
* 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}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.video;

import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.format;
import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
Expand All @@ -26,13 +27,16 @@
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.test.core.app.ApplicationProvider;
Expand Down Expand Up @@ -63,6 +67,8 @@
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}. */
Expand Down Expand Up @@ -610,6 +616,100 @@ public void supportsFormat_withDolbyVisionMedia_returnsTrueWhenFallbackToH265orH
.isEqualTo(C.FORMAT_UNSUPPORTED_SUBTYPE);
}

@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);
Expand Down

0 comments on commit a366590

Please sign in to comment.