Changed decoder list sort to order by functional support of format
Added new method to check if codec just functionally supports a format. Changed getDecoderInfosSortedByFormatSupport to use new function to order by functional support. This allows decoders that only support functionally and are more preferred by the MediaCodecSelector to keep their preferred position in the sorted list. UnitTests included -Two MediaCodecVideoRenderer tests that verify hw vs sw does not have an effect on sort of the decoder list, it is only based on functional support Issue: google/ExoPlayer#10604 PiperOrigin-RevId: 487779284
This commit is contained in:
parent
a34fdcf168
commit
1eb8a6b36e
@ -140,13 +140,13 @@ public interface RendererCapabilities {
|
||||
/** A mask to apply to {@link Capabilities} to obtain {@link DecoderSupport} only. */
|
||||
int MODE_SUPPORT_MASK = 0b11 << 7;
|
||||
/**
|
||||
* The renderer will use a decoder for fallback mimetype if possible as format's MIME type is
|
||||
* unsupported
|
||||
* The format's MIME type is unsupported and the renderer may use a decoder for a fallback MIME
|
||||
* type.
|
||||
*/
|
||||
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. */
|
||||
/** The format exceeds the primary decoder's capabilities but is supported by fallback decoder */
|
||||
int DECODER_SUPPORT_FALLBACK = 0;
|
||||
|
||||
/**
|
||||
|
@ -243,7 +243,8 @@ public final class MediaCodecInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the decoder may support decoding the given {@code format}.
|
||||
* Returns whether the decoder may support decoding the given {@code format} both functionally and
|
||||
* performantly.
|
||||
*
|
||||
* @param format The input media format.
|
||||
* @return Whether the decoder may support decoding the given {@code format}.
|
||||
@ -254,7 +255,7 @@ public final class MediaCodecInfo {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isCodecProfileAndLevelSupported(format)) {
|
||||
if (!isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -281,15 +282,24 @@ public final class MediaCodecInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the decoder may functionally support decoding the given {@code format}.
|
||||
*
|
||||
* @param format The input media format.
|
||||
* @return Whether the decoder may functionally support decoding the given {@code format}.
|
||||
*/
|
||||
public boolean isFormatFunctionallySupported(Format format) {
|
||||
return isSampleMimeTypeSupported(format)
|
||||
&& isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ false);
|
||||
}
|
||||
|
||||
private boolean isSampleMimeTypeSupported(Format format) {
|
||||
return mimeType.equals(format.sampleMimeType)
|
||||
|| mimeType.equals(MediaCodecUtil.getAlternativeCodecMimeType(format));
|
||||
}
|
||||
|
||||
private boolean isCodecProfileAndLevelSupported(Format format) {
|
||||
if (format.codecs == null) {
|
||||
return true;
|
||||
}
|
||||
private boolean isCodecProfileAndLevelSupported(
|
||||
Format format, boolean checkPerformanceCapabilities) {
|
||||
Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
|
||||
if (codecProfileAndLevel == null) {
|
||||
// If we don't know any better, we assume that the profile and level are supported.
|
||||
@ -325,7 +335,7 @@ public final class MediaCodecInfo {
|
||||
|
||||
for (CodecProfileLevel profileLevel : profileLevels) {
|
||||
if (profileLevel.profile == profile
|
||||
&& profileLevel.level >= level
|
||||
&& (profileLevel.level >= level || !checkPerformanceCapabilities)
|
||||
&& !needsProfileExcludedWorkaround(mimeType, profile)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1111,6 +1111,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
}
|
||||
codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
||||
|
||||
if (!codecInfo.isFormatSupported(inputFormat)) {
|
||||
Log.w(
|
||||
TAG,
|
||||
Util.formatInvariant(
|
||||
"Format exceeds selected codec's capabilities [%s, %s]",
|
||||
Format.toLogString(inputFormat), codecName));
|
||||
}
|
||||
|
||||
this.codecInfo = codecInfo;
|
||||
this.codecOperatingRate = codecOperatingRate;
|
||||
codecInputFormat = inputFormat;
|
||||
|
@ -188,22 +188,15 @@ public final class MediaCodecUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the provided decoder list sorted such that decoders with format support are
|
||||
* listed first. The returned list is modifiable for convenience.
|
||||
* Returns a copy of the provided decoder list sorted such that decoders with functional format
|
||||
* support are listed first. The returned list is modifiable for convenience.
|
||||
*/
|
||||
@CheckResult
|
||||
public static List<MediaCodecInfo> getDecoderInfosSortedByFormatSupport(
|
||||
List<MediaCodecInfo> decoderInfos, Format format) {
|
||||
decoderInfos = new ArrayList<>(decoderInfos);
|
||||
sortByScore(
|
||||
decoderInfos,
|
||||
decoderInfo -> {
|
||||
try {
|
||||
return decoderInfo.isFormatSupported(format) ? 1 : 0;
|
||||
} catch (DecoderQueryException e) {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
decoderInfos, decoderInfo -> decoderInfo.isFormatFunctionallySupported(format) ? 1 : 0);
|
||||
return decoderInfos;
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,7 @@ import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@ -83,6 +84,32 @@ public class MediaCodecVideoRendererTest {
|
||||
.setHeight(1080)
|
||||
.build();
|
||||
|
||||
private static final MediaCodecInfo H264_PROFILE8_LEVEL4_HW_MEDIA_CODEC_INFO =
|
||||
MediaCodecInfo.newInstance(
|
||||
/* name= */ "h264-codec-hw",
|
||||
/* mimeType= */ MimeTypes.VIDEO_H264,
|
||||
/* codecMimeType= */ MimeTypes.VIDEO_H264,
|
||||
/* capabilities= */ createCodecCapabilities(
|
||||
CodecProfileLevel.AVCProfileHigh, CodecProfileLevel.AVCLevel4),
|
||||
/* hardwareAccelerated= */ true,
|
||||
/* softwareOnly= */ false,
|
||||
/* vendor= */ false,
|
||||
/* forceDisableAdaptive= */ false,
|
||||
/* forceSecure= */ false);
|
||||
|
||||
private static final MediaCodecInfo H264_PROFILE8_LEVEL5_SW_MEDIA_CODEC_INFO =
|
||||
MediaCodecInfo.newInstance(
|
||||
/* name= */ "h264-codec-sw",
|
||||
/* mimeType= */ MimeTypes.VIDEO_H264,
|
||||
/* codecMimeType= */ MimeTypes.VIDEO_H264,
|
||||
/* capabilities= */ createCodecCapabilities(
|
||||
CodecProfileLevel.AVCProfileHigh, CodecProfileLevel.AVCLevel5),
|
||||
/* hardwareAccelerated= */ false,
|
||||
/* softwareOnly= */ true,
|
||||
/* vendor= */ false,
|
||||
/* forceDisableAdaptive= */ false,
|
||||
/* forceSecure= */ false);
|
||||
|
||||
private Looper testMainLooper;
|
||||
private Surface surface;
|
||||
private MediaCodecVideoRenderer mediaCodecVideoRenderer;
|
||||
@ -710,6 +737,100 @@ public class MediaCodecVideoRendererTest {
|
||||
.isEqualTo(RendererCapabilities.DECODER_SUPPORT_PRIMARY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDecoderInfo_withNonPerformantHardwareDecoder_returnsHardwareDecoderFirst()
|
||||
throws Exception {
|
||||
// AVC Format, Profile: 8, Level: 8192
|
||||
Format avcFormat =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||
.setCodecs("avc1.64002a")
|
||||
.build();
|
||||
// Provide hardware and software AVC decoders
|
||||
MediaCodecSelector mediaCodecSelector =
|
||||
(mimeType, requiresSecureDecoder, requiresTunnelingDecoder) -> {
|
||||
if (!mimeType.equals(MimeTypes.VIDEO_H264)) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
// Hardware decoder supports above format functionally but not performantly as
|
||||
// it supports MIME type & Profile but not Level
|
||||
// Software decoder supports format functionally and peformantly as it supports
|
||||
// MIME type, Profile, and Level(assuming resolution/frame rate support too)
|
||||
return ImmutableList.of(
|
||||
H264_PROFILE8_LEVEL4_HW_MEDIA_CODEC_INFO, H264_PROFILE8_LEVEL5_SW_MEDIA_CODEC_INFO);
|
||||
};
|
||||
MediaCodecVideoRenderer renderer =
|
||||
new MediaCodecVideoRenderer(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
mediaCodecSelector,
|
||||
/* allowedJoiningTimeMs= */ 0,
|
||||
/* eventHandler= */ new Handler(testMainLooper),
|
||||
/* eventListener= */ eventListener,
|
||||
/* maxDroppedFramesToNotify= */ 1);
|
||||
renderer.init(/* index= */ 0, PlayerId.UNSET);
|
||||
|
||||
List<MediaCodecInfo> mediaCodecInfoList =
|
||||
renderer.getDecoderInfos(mediaCodecSelector, avcFormat, false);
|
||||
@Capabilities int capabilities = renderer.supportsFormat(avcFormat);
|
||||
|
||||
assertThat(mediaCodecInfoList).hasSize(2);
|
||||
assertThat(mediaCodecInfoList.get(0).hardwareAccelerated).isTrue();
|
||||
assertThat(RendererCapabilities.getFormatSupport(capabilities)).isEqualTo(C.FORMAT_HANDLED);
|
||||
assertThat(RendererCapabilities.getDecoderSupport(capabilities))
|
||||
.isEqualTo(RendererCapabilities.DECODER_SUPPORT_FALLBACK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDecoderInfo_softwareDecoderPreferred_returnsSoftwareDecoderFirst()
|
||||
throws Exception {
|
||||
// AVC Format, Profile: 8, Level: 8192
|
||||
Format avcFormat =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||
.setCodecs("avc1.64002a")
|
||||
.build();
|
||||
// Provide software and hardware AVC decoders
|
||||
MediaCodecSelector mediaCodecSelector =
|
||||
(mimeType, requiresSecureDecoder, requiresTunnelingDecoder) -> {
|
||||
if (!mimeType.equals(MimeTypes.VIDEO_H264)) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
// Hardware decoder supports above format functionally but not performantly as
|
||||
// it supports MIME type & Profile but not Level
|
||||
// Software decoder supports format functionally and peformantly as it supports
|
||||
// MIME type, Profile, and Level(assuming resolution/frame rate support too)
|
||||
return ImmutableList.of(
|
||||
H264_PROFILE8_LEVEL5_SW_MEDIA_CODEC_INFO, H264_PROFILE8_LEVEL4_HW_MEDIA_CODEC_INFO);
|
||||
};
|
||||
MediaCodecVideoRenderer renderer =
|
||||
new MediaCodecVideoRenderer(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
mediaCodecSelector,
|
||||
/* allowedJoiningTimeMs= */ 0,
|
||||
/* eventHandler= */ new Handler(testMainLooper),
|
||||
/* eventListener= */ eventListener,
|
||||
/* maxDroppedFramesToNotify= */ 1);
|
||||
renderer.init(/* index= */ 0, PlayerId.UNSET);
|
||||
|
||||
List<MediaCodecInfo> mediaCodecInfoList =
|
||||
renderer.getDecoderInfos(mediaCodecSelector, avcFormat, false);
|
||||
@Capabilities int capabilities = renderer.supportsFormat(avcFormat);
|
||||
|
||||
assertThat(mediaCodecInfoList).hasSize(2);
|
||||
assertThat(mediaCodecInfoList.get(0).hardwareAccelerated).isFalse();
|
||||
assertThat(RendererCapabilities.getFormatSupport(capabilities)).isEqualTo(C.FORMAT_HANDLED);
|
||||
assertThat(RendererCapabilities.getDecoderSupport(capabilities))
|
||||
.isEqualTo(RendererCapabilities.DECODER_SUPPORT_PRIMARY);
|
||||
}
|
||||
|
||||
private static CodecCapabilities createCodecCapabilities(int profile, int level) {
|
||||
CodecCapabilities capabilities = new CodecCapabilities();
|
||||
capabilities.profileLevels = new CodecProfileLevel[] {new CodecProfileLevel()};
|
||||
capabilities.profileLevels[0].profile = profile;
|
||||
capabilities.profileLevels[0].level = level;
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCodecMaxInputSize_videoH263() {
|
||||
MediaCodecInfo codecInfo = createMediaCodecInfo(MimeTypes.VIDEO_H263);
|
||||
|
Loading…
x
Reference in New Issue
Block a user