Improve resolution fallback logic.

With the new version, we try the following before fixing resolution:

- Fix size alignment
- Try 3/4 the width and height
- Try 2/3 the width and height
- Try 1/2 the width and height

Also: align the resolution ends in 1 or 9 to 0.
PiperOrigin-RevId: 433206358
This commit is contained in:
claincly 2022-03-08 15:31:06 +00:00 committed by Ian Baker
parent a349f311d4
commit 2f4630a8e8
3 changed files with 80 additions and 22 deletions

View File

@ -234,7 +234,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
/* cost= */ (encoderInfo) -> {
@Nullable
Pair<Integer, Integer> closestSupportedResolution =
EncoderUtil.getClosestSupportedResolution(
EncoderUtil.getSupportedResolution(
encoderInfo, mimeType, requestedFormat.width, requestedFormat.height);
if (closestSupportedResolution == null) {
// Drops encoder.
@ -250,7 +250,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
// The supported resolution is the same for all remaining encoders.
Pair<Integer, Integer> finalResolution =
checkNotNull(
EncoderUtil.getClosestSupportedResolution(
EncoderUtil.getSupportedResolution(
filteredEncoders.get(0), mimeType, requestedFormat.width, requestedFormat.height));
int requestedBitrate =

View File

@ -65,15 +65,16 @@ public final class EncoderUtil {
}
/**
* Finds the {@link MediaCodecInfo encoder}'s closest supported resolution from the given
* resolution.
* Finds a {@link MediaCodecInfo encoder}'s supported resolution from a given resolution.
*
* <p>The input resolution is returned, if it is supported by the {@link MediaCodecInfo encoder}.
* <p>The input resolution is returned, if it (after aligning to the encoders requirement) is
* supported by the {@link MediaCodecInfo encoder}.
*
* <p>The resolution will be clamped to the {@link MediaCodecInfo encoder}'s range of supported
* resolutions, and adjusted to the {@link MediaCodecInfo encoder}'s size alignment. The
* adjustment process takes into account the original aspect ratio. But the fixed resolution may
* not preserve the original aspect ratio, depending on the encoder's required size alignment.
* <p>The resolution will be adjusted to be within the {@link MediaCodecInfo encoder}'s range of
* supported resolutions, and will be aligned to the {@link MediaCodecInfo encoder}'s alignment
* requirement. The adjustment process takes into account the original aspect ratio. But the fixed
* resolution may not preserve the original aspect ratio, depending on the encoder's required size
* alignment.
*
* @param encoderInfo The {@link MediaCodecInfo} of the encoder.
* @param mimeType The output MIME type.
@ -82,27 +83,50 @@ public final class EncoderUtil {
* @return A {@link Pair} of width and height, or {@code null} if unable to find a fix.
*/
@Nullable
public static Pair<Integer, Integer> getClosestSupportedResolution(
public static Pair<Integer, Integer> getSupportedResolution(
MediaCodecInfo encoderInfo, String mimeType, int width, int height) {
MediaCodecInfo.VideoCapabilities videoEncoderCapabilities =
encoderInfo.getCapabilitiesForType(mimeType).getVideoCapabilities();
int widthAlignment = videoEncoderCapabilities.getWidthAlignment();
int heightAlignment = videoEncoderCapabilities.getHeightAlignment();
// Fix size alignment.
width = alignResolution(width, widthAlignment);
height = alignResolution(height, heightAlignment);
if (videoEncoderCapabilities.isSizeSupported(width, height)) {
return Pair.create(width, height);
}
// Try three-fourths (e.g. 1440 -> 1080).
int newWidth = alignResolution(width * 3 / 4, widthAlignment);
int newHeight = alignResolution(height * 3 / 4, heightAlignment);
if (videoEncoderCapabilities.isSizeSupported(newWidth, newHeight)) {
return Pair.create(newWidth, newHeight);
}
// Try two-thirds (e.g. 4k -> 1440).
newWidth = alignResolution(width * 2 / 3, widthAlignment);
newHeight = alignResolution(height * 2 / 3, heightAlignment);
if (videoEncoderCapabilities.isSizeSupported(newWidth, newHeight)) {
return Pair.create(newWidth, newHeight);
}
// Try half (e.g. 4k -> 1080).
newWidth = alignResolution(width / 2, widthAlignment);
newHeight = alignResolution(height / 2, heightAlignment);
if (videoEncoderCapabilities.isSizeSupported(newWidth, newHeight)) {
return Pair.create(newWidth, newHeight);
}
// Fix frame being too wide or too tall.
width = videoEncoderCapabilities.getSupportedWidths().clamp(width);
int adjustedHeight = videoEncoderCapabilities.getSupportedHeightsFor(width).clamp(height);
if (adjustedHeight != height) {
width = (int) round((double) width * adjustedHeight / height);
height = adjustedHeight;
width =
alignResolution((int) round((double) width * adjustedHeight / height), widthAlignment);
height = alignResolution(adjustedHeight, heightAlignment);
}
// Fix pixel alignment.
width = alignResolution(width, videoEncoderCapabilities.getWidthAlignment());
height = alignResolution(height, videoEncoderCapabilities.getHeightAlignment());
return videoEncoderCapabilities.isSizeSupported(width, height)
? Pair.create(width, height)
: null;
@ -187,7 +211,15 @@ public final class EncoderUtil {
* aligned to 48.
*/
private static int alignResolution(int size, int alignment) {
return alignment * Math.round((float) size / alignment);
// Aligning to resolutions that are multiples of 10, like from 1081 to 1080, assuming alignment
// is 2 in most encoders.
boolean shouldRoundDown = false;
if (size % 10 == 1) {
shouldRoundDown = true;
}
return shouldRoundDown
? (int) (alignment * Math.floor((float) size / alignment))
: alignment * Math.round((float) size / alignment);
}
private static synchronized void maybePopulateEncoderInfos() {

View File

@ -62,28 +62,54 @@ public class EncoderUtilTest {
}
@Test
public void getClosestSupportedResolution_withSupportedResolution_succeeds() {
public void getSupportedResolution_withSupportedResolution_succeeds() {
ImmutableList<MediaCodecInfo> supportedEncoders = EncoderUtil.getSupportedEncoders(MIME_TYPE);
MediaCodecInfo encoderInfo = supportedEncoders.get(0);
@Nullable
Pair<Integer, Integer> closestSupportedResolution =
EncoderUtil.getClosestSupportedResolution(encoderInfo, MIME_TYPE, 1920, 1080);
EncoderUtil.getSupportedResolution(encoderInfo, MIME_TYPE, 1920, 1080);
assertThat(closestSupportedResolution).isNotNull();
assertThat(closestSupportedResolution).isEqualTo(Pair.create(1920, 1080));
}
@Test
public void getClosestSupportedResolution_withWidthTooBig_findsMostCloselySupportedResolution() {
public void getSupportedResolution_withUnalignedSize_findsMostCloselySupportedResolution() {
ImmutableList<MediaCodecInfo> supportedEncoders = EncoderUtil.getSupportedEncoders(MIME_TYPE);
MediaCodecInfo encoderInfo = supportedEncoders.get(0);
@Nullable
Pair<Integer, Integer> closestSupportedResolution =
EncoderUtil.getClosestSupportedResolution(encoderInfo, MIME_TYPE, 1920, 1920);
EncoderUtil.getSupportedResolution(encoderInfo, MIME_TYPE, 1919, 1081);
assertThat(closestSupportedResolution).isNotNull();
assertThat(closestSupportedResolution).isEqualTo(Pair.create(1088, 1088));
assertThat(closestSupportedResolution).isEqualTo(Pair.create(1920, 1080));
}
@Test
public void getSupportedResolution_withWidthTooBig_findsTwoThirdsOfTheOriginalSize() {
ImmutableList<MediaCodecInfo> supportedEncoders = EncoderUtil.getSupportedEncoders(MIME_TYPE);
MediaCodecInfo encoderInfo = supportedEncoders.get(0);
@Nullable
Pair<Integer, Integer> closestSupportedResolution =
EncoderUtil.getSupportedResolution(encoderInfo, MIME_TYPE, 1920, 1920);
assertThat(closestSupportedResolution).isNotNull();
assertThat(closestSupportedResolution).isEqualTo(Pair.create(1440, 1440));
}
@Test
public void getSupportedResolution_withWidthTooBig2_findsHalfOfTheOriginalSize() {
ImmutableList<MediaCodecInfo> supportedEncoders = EncoderUtil.getSupportedEncoders(MIME_TYPE);
MediaCodecInfo encoderInfo = supportedEncoders.get(0);
@Nullable
Pair<Integer, Integer> closestSupportedResolution =
EncoderUtil.getSupportedResolution(encoderInfo, MIME_TYPE, 3840, 2160);
assertThat(closestSupportedResolution).isNotNull();
assertThat(closestSupportedResolution).isEqualTo(Pair.create(1920, 1080));
}
}