Set max resolution from codec capabilities for ABR where resolutions are unknown
Issue: #2096 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=145542983
This commit is contained in:
parent
953c6855ec
commit
0a8dc41632
@ -183,7 +183,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) {
|
||||
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
|
||||
MediaCrypto crypto) {
|
||||
if (passthroughEnabled) {
|
||||
// Override the MIME type used to configure the codec if we are using a passthrough decoder.
|
||||
passthroughMediaFormat = format.getFrameworkMediaFormatV16();
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2.mediacodec;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Point;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo.AudioCapabilities;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
@ -23,6 +24,7 @@ import android.media.MediaCodecInfo.CodecProfileLevel;
|
||||
import android.media.MediaCodecInfo.VideoCapabilities;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -141,39 +143,6 @@ public final class MediaCodecInfo {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the decoder supports video with a specified width and height.
|
||||
* <p>
|
||||
* Must not be called if the device SDK version is less than 21.
|
||||
*
|
||||
* @param width Width in pixels.
|
||||
* @param height Height in pixels.
|
||||
* @return Whether the decoder supports video with the given width and height.
|
||||
*/
|
||||
@TargetApi(21)
|
||||
public boolean isVideoSizeSupportedV21(int width, int height) {
|
||||
if (capabilities == null) {
|
||||
logNoSupport("size.caps");
|
||||
return false;
|
||||
}
|
||||
VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
|
||||
if (videoCapabilities == null) {
|
||||
logNoSupport("size.vCaps");
|
||||
return false;
|
||||
}
|
||||
if (!videoCapabilities.isSizeSupported(width, height)) {
|
||||
// Capabilities are known to be inaccurately reported for vertical resolutions on some devices
|
||||
// (b/31387661). If the video is vertical and the capabilities indicate support if the width
|
||||
// and height are swapped, we assume that the vertical resolution is also supported.
|
||||
if (width >= height || !videoCapabilities.isSizeSupported(height, width)) {
|
||||
logNoSupport("size.support, " + width + "x" + height);
|
||||
return false;
|
||||
}
|
||||
logAssumedSupport("size.rotated, " + width + "x" + height);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the decoder supports video with a given width, height and frame rate.
|
||||
* <p>
|
||||
@ -181,7 +150,8 @@ public final class MediaCodecInfo {
|
||||
*
|
||||
* @param width Width in pixels.
|
||||
* @param height Height in pixels.
|
||||
* @param frameRate Frame rate in frames per second.
|
||||
* @param frameRate Optional frame rate in frames per second. Ignored if set to
|
||||
* {@link Format#NO_VALUE} or any value less than or equal to 0.
|
||||
* @return Whether the decoder supports video with the given width, height and frame rate.
|
||||
*/
|
||||
@TargetApi(21)
|
||||
@ -195,11 +165,12 @@ public final class MediaCodecInfo {
|
||||
logNoSupport("sizeAndRate.vCaps");
|
||||
return false;
|
||||
}
|
||||
if (!videoCapabilities.areSizeAndRateSupported(width, height, frameRate)) {
|
||||
if (!areSizeAndRateSupported(videoCapabilities, width, height, frameRate)) {
|
||||
// Capabilities are known to be inaccurately reported for vertical resolutions on some devices
|
||||
// (b/31387661). If the video is vertical and the capabilities indicate support if the width
|
||||
// and height are swapped, we assume that the vertical resolution is also supported.
|
||||
if (width >= height || !videoCapabilities.areSizeAndRateSupported(height, width, frameRate)) {
|
||||
if (width >= height
|
||||
|| !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) {
|
||||
logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate);
|
||||
return false;
|
||||
}
|
||||
@ -208,6 +179,35 @@ public final class MediaCodecInfo {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the smallest video size greater than or equal to a specified size that also satisfies
|
||||
* the {@link MediaCodec}'s width and height alignment requirements.
|
||||
* <p>
|
||||
* Must not be called if the device SDK version is less than 21.
|
||||
*
|
||||
* @param width Width in pixels.
|
||||
* @param height Height in pixels.
|
||||
* @return The smallest video size greater than or equal to the specified size that also satisfies
|
||||
* the {@link MediaCodec}'s width and height alignment requirements, or null if not a video
|
||||
* codec.
|
||||
*/
|
||||
@TargetApi(21)
|
||||
public Point alignVideoSizeV21(int width, int height) {
|
||||
if (capabilities == null) {
|
||||
logNoSupport("align.caps");
|
||||
return null;
|
||||
}
|
||||
VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
|
||||
if (videoCapabilities == null) {
|
||||
logNoSupport("align.vCaps");
|
||||
return null;
|
||||
}
|
||||
int widthAlignment = videoCapabilities.getWidthAlignment();
|
||||
int heightAlignment = videoCapabilities.getHeightAlignment();
|
||||
return new Point(Util.ceilDivide(width, widthAlignment) * widthAlignment,
|
||||
Util.ceilDivide(height, heightAlignment) * heightAlignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the decoder supports audio with a given sample rate.
|
||||
* <p>
|
||||
@ -279,6 +279,14 @@ public final class MediaCodecInfo {
|
||||
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
private static boolean areSizeAndRateSupported(VideoCapabilities capabilities, int width,
|
||||
int height, double frameRate) {
|
||||
return frameRate == Format.NO_VALUE || frameRate <= 0
|
||||
? capabilities.isSizeSupported(width, height)
|
||||
: capabilities.areSizeAndRateSupported(width, height, frameRate);
|
||||
}
|
||||
|
||||
private static boolean isTunneling(CodecCapabilities capabilities) {
|
||||
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
|
||||
}
|
||||
|
@ -276,11 +276,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
/**
|
||||
* Configures a newly created {@link MediaCodec}.
|
||||
*
|
||||
* @param codecInfo Information about the {@link MediaCodec} being configured.
|
||||
* @param codec The {@link MediaCodec} to configure.
|
||||
* @param format The format for which the codec is being configured.
|
||||
* @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption.
|
||||
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
|
||||
*/
|
||||
protected abstract void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto);
|
||||
protected abstract void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
|
||||
MediaCrypto crypto) throws DecoderQueryException;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
protected final void maybeInitCodec() throws ExoPlaybackException {
|
||||
@ -345,7 +348,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
codec = MediaCodec.createByCodecName(codecName);
|
||||
TraceUtil.endSection();
|
||||
TraceUtil.beginSection("configureCodec");
|
||||
configureCodec(codec, format, mediaCrypto);
|
||||
configureCodec(decoderInfo, codec, format, mediaCrypto);
|
||||
TraceUtil.endSection();
|
||||
TraceUtil.beginSection("startCodec");
|
||||
codec.start();
|
||||
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.video;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
import android.media.MediaCrypto;
|
||||
@ -56,6 +57,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
private static final String KEY_CROP_BOTTOM = "crop-bottom";
|
||||
private static final String KEY_CROP_TOP = "crop-top";
|
||||
|
||||
// Long edge length in pixels for standard video formats, in decreasing in order.
|
||||
private static final int[] STANDARD_LONG_EDGE_VIDEO_PX = new int[] {
|
||||
1920, 1600, 1440, 1280, 960, 854, 640, 540, 480};
|
||||
|
||||
private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final long allowedJoiningTimeMs;
|
||||
@ -186,12 +191,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs);
|
||||
if (decoderCapable && format.width > 0 && format.height > 0) {
|
||||
if (Util.SDK_INT >= 21) {
|
||||
if (format.frameRate > 0) {
|
||||
decoderCapable = decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height,
|
||||
format.frameRate);
|
||||
} else {
|
||||
decoderCapable = decoderInfo.isVideoSizeSupportedV21(format.width, format.height);
|
||||
}
|
||||
decoderCapable = decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height,
|
||||
format.frameRate);
|
||||
} else {
|
||||
decoderCapable = format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize();
|
||||
if (!decoderCapable) {
|
||||
@ -318,8 +319,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) {
|
||||
codecMaxValues = getCodecMaxValues(format, streamFormats);
|
||||
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
|
||||
MediaCrypto crypto) throws DecoderQueryException {
|
||||
codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats);
|
||||
MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround,
|
||||
tunnelingAudioSessionId);
|
||||
codec.configure(mediaFormat, surface, crypto, 0);
|
||||
@ -597,29 +599,92 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
* Returns {@link CodecMaxValues} suitable for configuring a codec for {@code format} in a way
|
||||
* that will allow possible adaptation to other compatible formats in {@code streamFormats}.
|
||||
*
|
||||
* @param codecInfo Information about the {@link MediaCodec} being configured.
|
||||
* @param format The format for which the codec is being configured.
|
||||
* @param streamFormats The possible stream formats.
|
||||
* @return Suitable {@link CodecMaxValues}.
|
||||
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
|
||||
*/
|
||||
private static CodecMaxValues getCodecMaxValues(Format format, Format[] streamFormats) {
|
||||
private static CodecMaxValues getCodecMaxValues(MediaCodecInfo codecInfo, Format format,
|
||||
Format[] streamFormats) throws DecoderQueryException {
|
||||
int maxWidth = format.width;
|
||||
int maxHeight = format.height;
|
||||
int maxInputSize = getMaxInputSize(format);
|
||||
if (streamFormats.length == 1) {
|
||||
// The single entry in streamFormats must correspond to the format for which the codec is
|
||||
// being configured.
|
||||
return new CodecMaxValues(maxWidth, maxHeight, maxInputSize);
|
||||
}
|
||||
boolean haveUnknownDimensions = false;
|
||||
for (Format streamFormat : streamFormats) {
|
||||
if (areAdaptationCompatible(format, streamFormat)) {
|
||||
haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE
|
||||
|| streamFormat.height == Format.NO_VALUE);
|
||||
maxWidth = Math.max(maxWidth, streamFormat.width);
|
||||
maxHeight = Math.max(maxHeight, streamFormat.height);
|
||||
maxInputSize = Math.max(maxInputSize, getMaxInputSize(streamFormat));
|
||||
}
|
||||
}
|
||||
if (haveUnknownDimensions) {
|
||||
Log.w(TAG, "Resolutions unknown. Codec max resolution: " + maxWidth + "x" + maxHeight);
|
||||
Point codecMaxSize = getCodecMaxSize(codecInfo, format);
|
||||
if (codecMaxSize != null) {
|
||||
maxWidth = Math.max(maxWidth, codecMaxSize.x);
|
||||
maxHeight = Math.max(maxHeight, codecMaxSize.y);
|
||||
maxInputSize = Math.max(maxInputSize,
|
||||
getMaxInputSize(format.sampleMimeType, maxWidth, maxHeight));
|
||||
Log.w(TAG, "Codec max resolution adjusted to: " + maxWidth + "x" + maxHeight);
|
||||
}
|
||||
}
|
||||
return new CodecMaxValues(maxWidth, maxHeight, maxInputSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a maximum video size to use when configuring a codec for {@code format} in a way
|
||||
* that will allow possible adaptation to other compatible formats that are expected to have the
|
||||
* same aspect ratio, but whose sizes are unknown.
|
||||
*
|
||||
* @param codecInfo Information about the {@link MediaCodec} being configured.
|
||||
* @param format The format for which the codec is being configured.
|
||||
* @return The maximum video size to use, or null if the size of {@code format} should be used.
|
||||
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
|
||||
*/
|
||||
private static Point getCodecMaxSize(MediaCodecInfo codecInfo, Format format)
|
||||
throws DecoderQueryException {
|
||||
boolean isVerticalVideo = format.height > format.width;
|
||||
int formatLongEdgePx = isVerticalVideo ? format.height : format.width;
|
||||
int formatShortEdgePx = isVerticalVideo ? format.width : format.height;
|
||||
float aspectRatio = (float) formatShortEdgePx / formatLongEdgePx;
|
||||
for (int longEdgePx : STANDARD_LONG_EDGE_VIDEO_PX) {
|
||||
int shortEdgePx = (int) (longEdgePx * aspectRatio);
|
||||
if (longEdgePx <= formatLongEdgePx || shortEdgePx <= formatShortEdgePx) {
|
||||
// Don't return a size not larger than the format for which the codec is being configured.
|
||||
return null;
|
||||
} else if (Util.SDK_INT >= 21) {
|
||||
Point alignedSize = codecInfo.alignVideoSizeV21(isVerticalVideo ? shortEdgePx : longEdgePx,
|
||||
isVerticalVideo ? longEdgePx : shortEdgePx);
|
||||
float frameRate = format.frameRate;
|
||||
if (codecInfo.isVideoSizeAndRateSupportedV21(alignedSize.x, alignedSize.y, frameRate)) {
|
||||
return alignedSize;
|
||||
}
|
||||
} else {
|
||||
// Conservatively assume the codec requires 16px width and height alignment.
|
||||
longEdgePx = Util.ceilDivide(longEdgePx, 16) * 16;
|
||||
shortEdgePx = Util.ceilDivide(shortEdgePx, 16) * 16;
|
||||
if (longEdgePx * shortEdgePx <= MediaCodecUtil.maxH264DecodableFrameSize()) {
|
||||
return new Point(isVerticalVideo ? shortEdgePx : longEdgePx,
|
||||
isVerticalVideo ? longEdgePx : shortEdgePx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a maximum input size for a given format.
|
||||
*
|
||||
* @param format The format.
|
||||
* @return An maximum input size in bytes, or {@link Format#NO_VALUE} if a maximum could not be
|
||||
* @return A maximum input size in bytes, or {@link Format#NO_VALUE} if a maximum could not be
|
||||
* determined.
|
||||
*/
|
||||
private static int getMaxInputSize(Format format) {
|
||||
@ -627,8 +692,20 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
// The format defines an explicit maximum input size.
|
||||
return format.maxInputSize;
|
||||
}
|
||||
return getMaxInputSize(format.sampleMimeType, format.width, format.height);
|
||||
}
|
||||
|
||||
if (format.width == Format.NO_VALUE || format.height == Format.NO_VALUE) {
|
||||
/**
|
||||
* Returns a maximum input size for a given mime type, width and height.
|
||||
*
|
||||
* @param sampleMimeType The format mime type.
|
||||
* @param width The width in pixels.
|
||||
* @param height The height in pixels.
|
||||
* @return A maximum input size in bytes, or {@link Format#NO_VALUE} if a maximum could not be
|
||||
* determined.
|
||||
*/
|
||||
private static int getMaxInputSize(String sampleMimeType, int width, int height) {
|
||||
if (width == Format.NO_VALUE || height == Format.NO_VALUE) {
|
||||
// We can't infer a maximum input size without video dimensions.
|
||||
return Format.NO_VALUE;
|
||||
}
|
||||
@ -636,10 +713,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
// Attempt to infer a maximum input size from the format.
|
||||
int maxPixels;
|
||||
int minCompressionRatio;
|
||||
switch (format.sampleMimeType) {
|
||||
switch (sampleMimeType) {
|
||||
case MimeTypes.VIDEO_H263:
|
||||
case MimeTypes.VIDEO_MP4V:
|
||||
maxPixels = format.width * format.height;
|
||||
maxPixels = width * height;
|
||||
minCompressionRatio = 2;
|
||||
break;
|
||||
case MimeTypes.VIDEO_H264:
|
||||
@ -649,17 +726,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
return Format.NO_VALUE;
|
||||
}
|
||||
// Round up width/height to an integer number of macroblocks.
|
||||
maxPixels = ((format.width + 15) / 16) * ((format.height + 15) / 16) * 16 * 16;
|
||||
maxPixels = Util.ceilDivide(width, 16) * Util.ceilDivide(height, 16) * 16 * 16;
|
||||
minCompressionRatio = 2;
|
||||
break;
|
||||
case MimeTypes.VIDEO_VP8:
|
||||
// VPX does not specify a ratio so use the values from the platform's SoftVPX.cpp.
|
||||
maxPixels = format.width * format.height;
|
||||
maxPixels = width * height;
|
||||
minCompressionRatio = 2;
|
||||
break;
|
||||
case MimeTypes.VIDEO_H265:
|
||||
case MimeTypes.VIDEO_VP9:
|
||||
maxPixels = format.width * format.height;
|
||||
maxPixels = width * height;
|
||||
minCompressionRatio = 4;
|
||||
break;
|
||||
default:
|
||||
|
Loading…
x
Reference in New Issue
Block a user