diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/analysis/EncoderPerformanceAnalysisTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/analysis/EncoderPerformanceAnalysisTest.java new file mode 100644 index 0000000000..11bd5818af --- /dev/null +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/analysis/EncoderPerformanceAnalysisTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.exoplayer2.transformer.mh.analysis; + +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + +import android.content.Context; +import android.media.MediaFormat; +import android.net.Uri; +import androidx.test.core.app.ApplicationProvider; +import com.google.android.exoplayer2.transformer.AndroidTestUtil; +import com.google.android.exoplayer2.transformer.DefaultEncoderFactory; +import com.google.android.exoplayer2.transformer.EncoderSelector; +import com.google.android.exoplayer2.transformer.Transformer; +import com.google.android.exoplayer2.transformer.TransformerAndroidTestRunner; +import com.google.android.exoplayer2.transformer.VideoEncoderSettings; +import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; +import java.util.HashMap; +import java.util.Map; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** Instrumentation tests for analyzing encoder performance settings. */ +@RunWith(Parameterized.class) +public class EncoderPerformanceAnalysisTest { + + /** A non-realtime {@link MediaFormat#KEY_PRIORITY encoder priority}. */ + private static final int MEDIA_CODEC_PRIORITY_NON_REALTIME = 0; + /** A realtime {@link MediaFormat#KEY_PRIORITY encoder priority}. */ + private static final int MEDIA_CODEC_PRIORITY_REALTIME = 1; + + private static final ImmutableList INPUT_FILES = + ImmutableList.of( + AndroidTestUtil.MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING, + AndroidTestUtil.MP4_REMOTE_4K60_PORTRAIT_URI_STRING); + + private static final ImmutableList OPERATING_RATE_SETTINGS = + ImmutableList.of(VideoEncoderSettings.NO_VALUE, 30, Integer.MAX_VALUE); + + private static final ImmutableList PRIORITY_SETTINGS = + ImmutableList.of( + // Use NO_VALUE to skip setting priority. + VideoEncoderSettings.NO_VALUE, + MEDIA_CODEC_PRIORITY_NON_REALTIME, + MEDIA_CODEC_PRIORITY_REALTIME); + + @Parameter(0) + public @MonotonicNonNull String fileUri; + + @Parameter(1) + public int operatingRate; + + @Parameter(2) + public int priority; + + @Parameters(name = "analyzePerformance_{0}_OpRate={1}_Priority={2}") + public static ImmutableList parameters() { + ImmutableList.Builder parametersBuilder = new ImmutableList.Builder<>(); + for (int i = 0; i < INPUT_FILES.size(); i++) { + for (int j = 0; j < OPERATING_RATE_SETTINGS.size(); j++) { + for (int k = 0; k < PRIORITY_SETTINGS.size(); k++) { + parametersBuilder.add( + new Object[] { + INPUT_FILES.get(i), OPERATING_RATE_SETTINGS.get(j), PRIORITY_SETTINGS.get(k) + }); + } + } + } + return parametersBuilder.build(); + } + + @Test + public void analyzeEncoderPerformance() throws Exception { + checkNotNull(fileUri); + String filename = checkNotNull(Uri.parse(fileUri).getLastPathSegment()); + String testId = + Util.formatInvariant( + "analyzePerformance_%s_OpRate_%d_Priority_%d", filename, operatingRate, priority); + + Map inputValues = new HashMap<>(); + inputValues.put("inputFilename", filename); + inputValues.put("operatingRate", operatingRate); + inputValues.put("priority", priority); + + Context context = ApplicationProvider.getApplicationContext(); + Transformer transformer = + new Transformer.Builder(context) + .setRemoveAudio(true) + .setEncoderFactory( + new DefaultEncoderFactory( + EncoderSelector.DEFAULT, + new VideoEncoderSettings.Builder() + .setEncoderPerformanceParameters(operatingRate, priority) + .build(), + /* enableFallback= */ false)) + .build(); + + new TransformerAndroidTestRunner.Builder(context, transformer) + .setInputValues(inputValues) + .build() + .run(testId, fileUri); + } +} diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java index e284f023a6..1e51e0d854 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java @@ -183,6 +183,17 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { mediaFormat.setFloat( MediaFormat.KEY_I_FRAME_INTERVAL, supportedVideoEncoderSettings.iFrameIntervalSeconds); + if (Util.SDK_INT >= 23) { + // Setting operating rate and priority is supported from API 23. + if (supportedVideoEncoderSettings.operatingRate != VideoEncoderSettings.NO_VALUE) { + mediaFormat.setInteger( + MediaFormat.KEY_OPERATING_RATE, supportedVideoEncoderSettings.operatingRate); + } + if (supportedVideoEncoderSettings.priority != VideoEncoderSettings.NO_VALUE) { + mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, supportedVideoEncoderSettings.priority); + } + } + return new DefaultCodec( format, mediaFormat, diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java index 2fe4a52327..7a8d615aed 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java @@ -21,8 +21,10 @@ import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; import android.media.MediaCodecInfo; +import android.media.MediaFormat; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.Format; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -74,6 +76,8 @@ public final class VideoEncoderSettings { private int level; private int colorProfile; private float iFrameIntervalSeconds; + private int operatingRate; + private int priority; /** Creates a new instance. */ public Builder() { @@ -83,6 +87,8 @@ public final class VideoEncoderSettings { this.level = NO_VALUE; this.colorProfile = DEFAULT_COLOR_PROFILE; this.iFrameIntervalSeconds = DEFAULT_I_FRAME_INTERVAL_SECONDS; + this.operatingRate = NO_VALUE; + this.priority = NO_VALUE; } private Builder(VideoEncoderSettings videoEncoderSettings) { @@ -92,6 +98,8 @@ public final class VideoEncoderSettings { this.level = videoEncoderSettings.level; this.colorProfile = videoEncoderSettings.colorProfile; this.iFrameIntervalSeconds = videoEncoderSettings.iFrameIntervalSeconds; + this.operatingRate = videoEncoderSettings.operatingRate; + this.priority = videoEncoderSettings.priority; } /** @@ -170,10 +178,31 @@ public final class VideoEncoderSettings { return this; } + /** + * Sets encoding operating rate and priority. The default values are {@link #NO_VALUE}. + * + * @param operatingRate The {@link MediaFormat#KEY_OPERATING_RATE operating rate}. + * @param priority The {@link MediaFormat#KEY_PRIORITY priority}. + * @return This builder. + */ + @VisibleForTesting + public Builder setEncoderPerformanceParameters(int operatingRate, int priority) { + this.operatingRate = operatingRate; + this.priority = priority; + return this; + } + /** Builds the instance. */ public VideoEncoderSettings build() { return new VideoEncoderSettings( - bitrate, bitrateMode, profile, level, colorProfile, iFrameIntervalSeconds); + bitrate, + bitrateMode, + profile, + level, + colorProfile, + iFrameIntervalSeconds, + operatingRate, + priority); } } @@ -189,6 +218,10 @@ public final class VideoEncoderSettings { public final int colorProfile; /** The encoding I-Frame interval in seconds. */ public final float iFrameIntervalSeconds; + /** The encoder {@link MediaFormat#KEY_OPERATING_RATE operating rate}. */ + public final int operatingRate; + /** The encoder {@link MediaFormat#KEY_PRIORITY priority}. */ + public final int priority; private VideoEncoderSettings( int bitrate, @@ -196,13 +229,17 @@ public final class VideoEncoderSettings { int profile, int level, int colorProfile, - float iFrameIntervalSeconds) { + float iFrameIntervalSeconds, + int operatingRate, + int priority) { this.bitrate = bitrate; this.bitrateMode = bitrateMode; this.profile = profile; this.level = level; this.colorProfile = colorProfile; this.iFrameIntervalSeconds = iFrameIntervalSeconds; + this.operatingRate = operatingRate; + this.priority = priority; } /** @@ -226,7 +263,9 @@ public final class VideoEncoderSettings { && profile == that.profile && level == that.level && colorProfile == that.colorProfile - && iFrameIntervalSeconds == that.iFrameIntervalSeconds; + && iFrameIntervalSeconds == that.iFrameIntervalSeconds + && operatingRate == that.operatingRate + && priority == that.priority; } @Override @@ -238,6 +277,8 @@ public final class VideoEncoderSettings { result = 31 * result + level; result = 31 * result + colorProfile; result = 31 * result + Float.floatToIntBits(iFrameIntervalSeconds); + result = 31 * result + operatingRate; + result = 31 * result + priority; return result; } }