Apply priority/operating rate settings for video encoding.

- Added setter to disable this feature.
- Added accompanying tests.
- Plan to run tests on the same set of settings on H265.

PiperOrigin-RevId: 460238673
(cherry picked from commit 18f4068c06a27ceedce8ee951b3832235a96f75e)
This commit is contained in:
claincly 2022-07-11 17:01:33 +00:00 committed by microkatz
parent 65d653c577
commit 627f26adef
4 changed files with 85 additions and 12 deletions

View File

@ -27,6 +27,7 @@ import android.media.MediaCodec.BufferInfo;
import android.media.MediaFormat;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
@ -277,6 +278,11 @@ public final class DefaultCodec implements Codec {
return mediaCodec.getName();
}
@VisibleForTesting
/* package */ MediaFormat getConfigurationMediaFormat() {
return configurationMediaFormat;
}
/**
* Attempts to dequeue an output buffer if there is no output buffer pending. Does nothing
* otherwise.

View File

@ -20,7 +20,6 @@ import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.SDK_INT;
import static java.lang.Math.abs;
import static java.lang.Math.floor;
import static java.lang.Math.round;
@ -47,6 +46,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@UnstableApi
public final class DefaultEncoderFactory implements Codec.EncoderFactory {
private static final int DEFAULT_FRAME_RATE = 30;
/** Best effort, or as-fast-as-possible priority setting for {@link MediaFormat#KEY_PRIORITY}. */
private static final int PRIORITY_BEST_EFFORT = 1;
private static final String TAG = "DefaultEncoderFactory";
/** A builder for {@link DefaultEncoderFactory} instances. */
@ -254,7 +256,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
if (supportedVideoEncoderSettings.profile != VideoEncoderSettings.NO_VALUE
&& supportedVideoEncoderSettings.level != VideoEncoderSettings.NO_VALUE
&& SDK_INT >= 23) {
&& Util.SDK_INT >= 23) {
// Set profile and level at the same time to maximize compatibility, or the encoder will pick
// the values.
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, supportedVideoEncoderSettings.profile);
@ -285,6 +287,10 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
if (Util.SDK_INT >= 23) {
// Setting operating rate and priority is supported from API 23.
if (supportedVideoEncoderSettings.operatingRate == VideoEncoderSettings.NO_VALUE
&& supportedVideoEncoderSettings.priority == VideoEncoderSettings.NO_VALUE) {
adjustMediaFormatForEncoderPerformanceSettings(mediaFormat);
} else {
if (supportedVideoEncoderSettings.operatingRate != VideoEncoderSettings.NO_VALUE) {
mediaFormat.setInteger(
MediaFormat.KEY_OPERATING_RATE, supportedVideoEncoderSettings.operatingRate);
@ -293,6 +299,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, supportedVideoEncoderSettings.priority);
}
}
}
return new DefaultCodec(
context,
@ -462,6 +469,28 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
}
}
/**
* Applies empirical {@link MediaFormat#KEY_PRIORITY} and {@link MediaFormat#KEY_OPERATING_RATE}
* settings for better encoder performance.
*
* <p>The adjustment is applied in-place to {@code mediaFormat}.
*/
private static void adjustMediaFormatForEncoderPerformanceSettings(MediaFormat mediaFormat) {
// TODO(b/213477153) Verify priority/operating rate settings work for non-AVC codecs.
if (Util.SDK_INT < 25) {
// Not setting priority and operating rate achieves better encoding performance.
return;
}
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, PRIORITY_BEST_EFFORT);
if (Util.SDK_INT == 26) {
mediaFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, DEFAULT_FRAME_RATE);
} else {
mediaFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Integer.MAX_VALUE);
}
}
/**
* Applying suggested profile/level settings from
* https://developer.android.com/guide/topics/media/sharing-video#b-frames_and_encoding_profiles

View File

@ -180,7 +180,8 @@ public final class VideoEncoderSettings {
}
/**
* Sets encoding operating rate and priority. The default values are {@link #NO_VALUE}.
* Sets encoding operating rate and priority. The default values are {@link #NO_VALUE}, which is
* treated as configuring the encoder for maximum throughput.
*
* @param operatingRate The {@link MediaFormat#KEY_OPERATING_RATE operating rate}.
* @param priority The {@link MediaFormat#KEY_PRIORITY priority}.

View File

@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodecList;
@ -40,6 +41,10 @@ public class DefaultEncoderFactoryTest {
@Before
public void setUp() {
createShadowH264Encoder();
}
private static void createShadowH264Encoder() {
MediaFormat avcFormat = new MediaFormat();
avcFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
MediaCodecInfo.CodecProfileLevel profileLevel = new MediaCodecInfo.CodecProfileLevel();
@ -48,17 +53,26 @@ public class DefaultEncoderFactoryTest {
// blocks will be left for encoding height 1088.
profileLevel.level = MediaCodecInfo.CodecProfileLevel.AVCLevel4;
createShadowVideoEncoder(avcFormat, profileLevel, "test.transformer.avc.encoder");
}
private static void createShadowVideoEncoder(
MediaFormat supportedFormat,
MediaCodecInfo.CodecProfileLevel supportedProfileLevel,
String name) {
// ShadowMediaCodecList is static. The added encoders will be visible for every test.
ShadowMediaCodecList.addCodec(
MediaCodecInfoBuilder.newBuilder()
.setName("test.transformer.avc.encoder")
.setName(name)
.setIsEncoder(true)
.setCapabilities(
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder()
.setMediaFormat(avcFormat)
.setMediaFormat(supportedFormat)
.setIsEncoder(true)
.setColorFormats(
new int[] {MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible})
.setProfileLevels(new MediaCodecInfo.CodecProfileLevel[] {profileLevel})
.setProfileLevels(
new MediaCodecInfo.CodecProfileLevel[] {supportedProfileLevel})
.build())
.build());
}
@ -117,6 +131,29 @@ public class DefaultEncoderFactoryTest {
assertThat(actualVideoFormat.height).isEqualTo(1080);
}
@Config(sdk = 29)
@Test
public void
createForVideoEncoding_withH264Encoding_configuresEncoderWithCorrectPerformanceSettings()
throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
Codec videoEncoder =
new DefaultEncoderFactory.Builder(context)
.build()
.createForVideoEncoding(
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264));
assertThat(videoEncoder).isInstanceOf(DefaultCodec.class);
MediaFormat configurationMediaFormat =
((DefaultCodec) videoEncoder).getConfigurationMediaFormat();
assertThat(configurationMediaFormat.containsKey(MediaFormat.KEY_PRIORITY)).isTrue();
assertThat(configurationMediaFormat.getInteger(MediaFormat.KEY_PRIORITY)).isEqualTo(1);
assertThat(configurationMediaFormat.containsKey(MediaFormat.KEY_OPERATING_RATE)).isTrue();
assertThat(configurationMediaFormat.getInteger(MediaFormat.KEY_OPERATING_RATE))
.isEqualTo(Integer.MAX_VALUE);
}
@Test
public void createForVideoEncoding_withNoSupportedEncoder_throws() {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);