Fix VideoEncoderSetting.bitrate is ignored when set

PiperOrigin-RevId: 491377695
This commit is contained in:
claincly 2022-11-28 18:16:19 +00:00 committed by Rohit Singh
parent 3581ccde29
commit 0f85ce5dbf
2 changed files with 169 additions and 37 deletions

View File

@ -206,6 +206,25 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
/* outputSurface= */ null); /* outputSurface= */ null);
} }
/**
* Returns a {@link DefaultCodec} for video encoding.
*
* <p>Use {@link Builder#setRequestedVideoEncoderSettings} with {@link
* VideoEncoderSettings#bitrate} set to request for a specific encoding bitrate. Bitrate settings
* in {@link Format} are ignored when {@link VideoEncoderSettings#bitrate} or {@link
* VideoEncoderSettings#enableHighQualityTargeting} is set.
*
* @param format The {@link Format} (of the output data) used to determine the underlying encoder
* and its configuration values. {@link Format#sampleMimeType}, {@link Format#width} and
* {@link Format#height} are set to those of the desired output video format. {@link
* Format#rotationDegrees} is 0 and {@link Format#width} {@code >=} {@link Format#height},
* therefore the video is always in landscape orientation. {@link Format#frameRate} is set to
* the output video's frame rate, if available.
* @param allowedMimeTypes The non-empty list of allowed output sample {@linkplain MimeTypes MIME
* types}.
* @return A {@link DefaultCodec} for video encoding.
* @throws TransformationException If no suitable {@link DefaultCodec} can be created.
*/
@Override @Override
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes)
throws TransformationException { throws TransformationException {
@ -246,26 +265,35 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
mimeType, encoderSupportedFormat.width, encoderSupportedFormat.height); mimeType, encoderSupportedFormat.width, encoderSupportedFormat.height);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, round(encoderSupportedFormat.frameRate)); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, round(encoderSupportedFormat.frameRate));
if (supportedVideoEncoderSettings.enableHighQualityTargeting) { int finalBitrate;
int bitrate = if (enableFallback) {
new DeviceMappedEncoderBitrateProvider() finalBitrate = supportedVideoEncoderSettings.bitrate;
.getBitrate( } else {
encoderInfo.getName(), // supportedVideoEncoderSettings is identical to requestedVideoEncoderSettings.
encoderSupportedFormat.width, if (supportedVideoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE) {
encoderSupportedFormat.height, finalBitrate = supportedVideoEncoderSettings.bitrate;
encoderSupportedFormat.frameRate); } else if (supportedVideoEncoderSettings.enableHighQualityTargeting) {
encoderSupportedFormat = finalBitrate =
encoderSupportedFormat.buildUpon().setAverageBitrate(bitrate).build(); new DeviceMappedEncoderBitrateProvider()
} else if (encoderSupportedFormat.bitrate == Format.NO_VALUE) { .getBitrate(
int bitrate = encoderInfo.getName(),
getSuggestedBitrate( encoderSupportedFormat.width,
encoderSupportedFormat.width, encoderSupportedFormat.height,
encoderSupportedFormat.height, encoderSupportedFormat.frameRate);
encoderSupportedFormat.frameRate); } else if (encoderSupportedFormat.averageBitrate != Format.NO_VALUE) {
encoderSupportedFormat = finalBitrate = encoderSupportedFormat.averageBitrate;
encoderSupportedFormat.buildUpon().setAverageBitrate(bitrate).build(); } else {
finalBitrate =
getSuggestedBitrate(
encoderSupportedFormat.width,
encoderSupportedFormat.height,
encoderSupportedFormat.frameRate);
}
} }
encoderSupportedFormat =
encoderSupportedFormat.buildUpon().setAverageBitrate(finalBitrate).build();
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, encoderSupportedFormat.averageBitrate); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, encoderSupportedFormat.averageBitrate);
mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, supportedVideoEncoderSettings.bitrateMode); mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, supportedVideoEncoderSettings.bitrateMode);
@ -389,16 +417,23 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
requestedFormat.width, requestedFormat.width,
requestedFormat.height)); requestedFormat.height));
int requestedBitrate = int requestedBitrate = Format.NO_VALUE;
videoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE // Encoders are not filtered by bitrate if high quality targeting is enabled.
? videoEncoderSettings.bitrate if (!videoEncoderSettings.enableHighQualityTargeting) {
: getSuggestedBitrate( requestedBitrate =
finalResolution.getWidth(), finalResolution.getHeight(), requestedFormat.frameRate); videoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE
? videoEncoderSettings.bitrate
filteredEncoderInfos = : requestedFormat.averageBitrate != Format.NO_VALUE
filterEncodersByBitrate(filteredEncoderInfos, mimeType, requestedBitrate); ? requestedFormat.averageBitrate
if (filteredEncoderInfos.isEmpty()) { : getSuggestedBitrate(
return null; finalResolution.getWidth(),
finalResolution.getHeight(),
requestedFormat.frameRate);
filteredEncoderInfos =
filterEncodersByBitrate(filteredEncoderInfos, mimeType, requestedBitrate);
if (filteredEncoderInfos.isEmpty()) {
return null;
}
} }
filteredEncoderInfos = filteredEncoderInfos =
@ -408,11 +443,6 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
return null; return null;
} }
// TODO(b/238094555): Check encoder supports bitrate targeted by high quality.
MediaCodecInfo pickedEncoderInfo = filteredEncoderInfos.get(0);
int closestSupportedBitrate =
EncoderUtil.getSupportedBitrateRange(pickedEncoderInfo, mimeType).clamp(requestedBitrate);
VideoEncoderSettings.Builder supportedEncodingSettingBuilder = videoEncoderSettings.buildUpon(); VideoEncoderSettings.Builder supportedEncodingSettingBuilder = videoEncoderSettings.buildUpon();
Format.Builder encoderSupportedFormatBuilder = Format.Builder encoderSupportedFormatBuilder =
requestedFormat requestedFormat
@ -420,11 +450,23 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
.setSampleMimeType(mimeType) .setSampleMimeType(mimeType)
.setWidth(finalResolution.getWidth()) .setWidth(finalResolution.getWidth())
.setHeight(finalResolution.getHeight()); .setHeight(finalResolution.getHeight());
MediaCodecInfo pickedEncoderInfo = filteredEncoderInfos.get(0);
if (!videoEncoderSettings.enableHighQualityTargeting) { if (videoEncoderSettings.enableHighQualityTargeting) {
supportedEncodingSettingBuilder.setBitrate(closestSupportedBitrate); requestedBitrate =
encoderSupportedFormatBuilder.setAverageBitrate(closestSupportedBitrate); new DeviceMappedEncoderBitrateProvider()
.getBitrate(
pickedEncoderInfo.getName(),
finalResolution.getWidth(),
finalResolution.getHeight(),
requestedFormat.frameRate);
// Resets the flag after getting a targeted bitrate, so that supportedEncodingSetting can have
// bitrate set.
supportedEncodingSettingBuilder.setEnableHighQualityTargeting(false);
} }
int closestSupportedBitrate =
EncoderUtil.getSupportedBitrateRange(pickedEncoderInfo, mimeType).clamp(requestedBitrate);
supportedEncodingSettingBuilder.setBitrate(closestSupportedBitrate);
encoderSupportedFormatBuilder.setAverageBitrate(closestSupportedBitrate);
if (videoEncoderSettings.profile == VideoEncoderSettings.NO_VALUE if (videoEncoderSettings.profile == VideoEncoderSettings.NO_VALUE
|| videoEncoderSettings.level == VideoEncoderSettings.NO_VALUE || videoEncoderSettings.level == VideoEncoderSettings.NO_VALUE

View File

@ -131,6 +131,96 @@ public class DefaultEncoderFactoryTest {
assertThat(actualVideoFormat.height).isEqualTo(1080); assertThat(actualVideoFormat.height).isEqualTo(1080);
} }
@Test
public void
createForVideoEncoding_setFormatAverageBitrateUnsetVideoEncoderSettings_configuresEncoderUsingFormatAverageBitrate()
throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
requestedVideoFormat = requestedVideoFormat.buildUpon().setAverageBitrate(5_000_000).build();
Format actualVideoFormat =
new DefaultEncoderFactory.Builder(context)
.setRequestedVideoEncoderSettings(VideoEncoderSettings.DEFAULT)
.build()
.createForVideoEncoding(
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264);
assertThat(actualVideoFormat.width).isEqualTo(1920);
assertThat(actualVideoFormat.height).isEqualTo(1080);
assertThat(actualVideoFormat.averageBitrate).isEqualTo(5_000_000);
}
@Test
public void
createForVideoEncoding_unsetFormatAverageBitrateAndUnsetVideoEncoderSettingsBitrate_configuresEncoderUsingDefaultBitrateMapping()
throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
Format actualVideoFormat =
new DefaultEncoderFactory.Builder(context)
.build()
.createForVideoEncoding(
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264);
assertThat(actualVideoFormat.width).isEqualTo(1920);
assertThat(actualVideoFormat.height).isEqualTo(1080);
// The default behavior is to use DefaultEncoderFactory#getSuggestedBitrate.
// 1920 * 1080 * 30 * 0.07 * 2.
assertThat(actualVideoFormat.averageBitrate).isEqualTo(8_709_120);
}
@Test
public void
createForVideoEncoding_setFormatAverageBitrateAndSetVideoEncoderSettingHighQualityTargeting_configuresEncoderUsingHighQualityTargeting()
throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
requestedVideoFormat = requestedVideoFormat.buildUpon().setAverageBitrate(5_000_000).build();
Format actualVideoFormat =
new DefaultEncoderFactory.Builder(context)
.setRequestedVideoEncoderSettings(
new VideoEncoderSettings.Builder().setEnableHighQualityTargeting(true).build())
.build()
.createForVideoEncoding(
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264);
assertThat(actualVideoFormat.width).isEqualTo(1920);
assertThat(actualVideoFormat.height).isEqualTo(1080);
// DeviceMappedEncoderBitrateProvider will produce 1920 * 1080 * 30 * 1.4, but the value is
// clampped down to the encoder's maximum, 25_000_000.
assertThat(actualVideoFormat.averageBitrate).isEqualTo(25_000_000);
}
@Test
public void
createForVideoEncoding_setFormatAverageBitrateAndVideoEncoderSettingsBitrate_configuresEncoderUsingVideoEncoderSettingsBitrate()
throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
requestedVideoFormat = requestedVideoFormat.buildUpon().setAverageBitrate(5_000_000).build();
Format actualVideoFormat =
new DefaultEncoderFactory.Builder(context)
.setRequestedVideoEncoderSettings(
new VideoEncoderSettings.Builder().setBitrate(10_000_000).build())
.build()
.createForVideoEncoding(
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264);
assertThat(actualVideoFormat.width).isEqualTo(1920);
assertThat(actualVideoFormat.height).isEqualTo(1080);
assertThat(actualVideoFormat.averageBitrate).isEqualTo(10_000_000);
}
@Config(sdk = 29) @Config(sdk = 29)
@Test @Test
public void public void