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.media.MediaFormat;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
@ -277,6 +278,11 @@ public final class DefaultCodec implements Codec {
return mediaCodec.getName(); return mediaCodec.getName();
} }
@VisibleForTesting
/* package */ MediaFormat getConfigurationMediaFormat() {
return configurationMediaFormat;
}
/** /**
* Attempts to dequeue an output buffer if there is no output buffer pending. Does nothing * Attempts to dequeue an output buffer if there is no output buffer pending. Does nothing
* otherwise. * 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.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull; 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.abs;
import static java.lang.Math.floor; import static java.lang.Math.floor;
import static java.lang.Math.round; import static java.lang.Math.round;
@ -47,6 +46,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@UnstableApi @UnstableApi
public final class DefaultEncoderFactory implements Codec.EncoderFactory { public final class DefaultEncoderFactory implements Codec.EncoderFactory {
private static final int DEFAULT_FRAME_RATE = 30; 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"; private static final String TAG = "DefaultEncoderFactory";
/** A builder for {@link DefaultEncoderFactory} instances. */ /** A builder for {@link DefaultEncoderFactory} instances. */
@ -254,7 +256,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
if (supportedVideoEncoderSettings.profile != VideoEncoderSettings.NO_VALUE if (supportedVideoEncoderSettings.profile != VideoEncoderSettings.NO_VALUE
&& supportedVideoEncoderSettings.level != 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 // Set profile and level at the same time to maximize compatibility, or the encoder will pick
// the values. // the values.
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, supportedVideoEncoderSettings.profile); mediaFormat.setInteger(MediaFormat.KEY_PROFILE, supportedVideoEncoderSettings.profile);
@ -285,6 +287,10 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
if (Util.SDK_INT >= 23) { if (Util.SDK_INT >= 23) {
// Setting operating rate and priority is supported from API 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) { if (supportedVideoEncoderSettings.operatingRate != VideoEncoderSettings.NO_VALUE) {
mediaFormat.setInteger( mediaFormat.setInteger(
MediaFormat.KEY_OPERATING_RATE, supportedVideoEncoderSettings.operatingRate); MediaFormat.KEY_OPERATING_RATE, supportedVideoEncoderSettings.operatingRate);
@ -293,6 +299,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, supportedVideoEncoderSettings.priority); mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, supportedVideoEncoderSettings.priority);
} }
} }
}
return new DefaultCodec( return new DefaultCodec(
context, 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 * Applying suggested profile/level settings from
* https://developer.android.com/guide/topics/media/sharing-video#b-frames_and_encoding_profiles * 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 operatingRate The {@link MediaFormat#KEY_OPERATING_RATE operating rate}.
* @param priority The {@link MediaFormat#KEY_PRIORITY priority}. * @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.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.MediaCodecInfoBuilder; import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodecList; import org.robolectric.shadows.ShadowMediaCodecList;
@ -40,6 +41,10 @@ public class DefaultEncoderFactoryTest {
@Before @Before
public void setUp() { public void setUp() {
createShadowH264Encoder();
}
private static void createShadowH264Encoder() {
MediaFormat avcFormat = new MediaFormat(); MediaFormat avcFormat = new MediaFormat();
avcFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); avcFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
MediaCodecInfo.CodecProfileLevel profileLevel = new MediaCodecInfo.CodecProfileLevel(); MediaCodecInfo.CodecProfileLevel profileLevel = new MediaCodecInfo.CodecProfileLevel();
@ -48,17 +53,26 @@ public class DefaultEncoderFactoryTest {
// blocks will be left for encoding height 1088. // blocks will be left for encoding height 1088.
profileLevel.level = MediaCodecInfo.CodecProfileLevel.AVCLevel4; 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( ShadowMediaCodecList.addCodec(
MediaCodecInfoBuilder.newBuilder() MediaCodecInfoBuilder.newBuilder()
.setName("test.transformer.avc.encoder") .setName(name)
.setIsEncoder(true) .setIsEncoder(true)
.setCapabilities( .setCapabilities(
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder() MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder()
.setMediaFormat(avcFormat) .setMediaFormat(supportedFormat)
.setIsEncoder(true) .setIsEncoder(true)
.setColorFormats( .setColorFormats(
new int[] {MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible}) new int[] {MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible})
.setProfileLevels(new MediaCodecInfo.CodecProfileLevel[] {profileLevel}) .setProfileLevels(
new MediaCodecInfo.CodecProfileLevel[] {supportedProfileLevel})
.build()) .build())
.build()); .build());
} }
@ -117,6 +131,29 @@ public class DefaultEncoderFactoryTest {
assertThat(actualVideoFormat.height).isEqualTo(1080); 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 @Test
public void createForVideoEncoding_withNoSupportedEncoder_throws() { public void createForVideoEncoding_withNoSupportedEncoder_throws() {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);