From e699765df5d1390c6c680eb5955e562eaf884825 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Tue, 29 Mar 2022 16:19:48 +0100 Subject: [PATCH] Add support for analyzing bitrates across devices. Allows for input values to be propagated to the analysis file. PiperOrigin-RevId: 438030322 --- .../TransformerAndroidTestRunner.java | 32 ++++- .../mh/analysis/BitrateAnalysisTest.java | 118 ++++++++++++++++++ .../transformer/mh/analysis/package-info.java | 19 +++ 3 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/BitrateAnalysisTest.java create mode 100644 libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/package-info.java diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java index 9b7a52625d..9211149761 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java @@ -30,6 +30,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; @@ -51,6 +52,7 @@ public class TransformerAndroidTestRunner { private boolean calculateSsim; private int timeoutSeconds; private boolean suppressAnalysisExceptions; + @Nullable private Map inputValues; /** * Creates a {@link Builder}. @@ -114,10 +116,30 @@ public class TransformerAndroidTestRunner { return this; } + /** + * Sets a {@link Map} of transformer input values, which are propagated to the transformation + * summary JSON file. + * + *

Values in the map should be convertible according to {@link JSONObject#wrap(Object)} to be + * recorded properly in the summary file. + * + * @param inputValues A {@link Map} of values to be written to the transformation summary. + * @return This {@link Builder}. + */ + public Builder setInputValues(@Nullable Map inputValues) { + this.inputValues = inputValues; + return this; + } + /** Builds the {@link TransformerAndroidTestRunner}. */ public TransformerAndroidTestRunner build() { return new TransformerAndroidTestRunner( - context, transformer, timeoutSeconds, calculateSsim, suppressAnalysisExceptions); + context, + transformer, + timeoutSeconds, + calculateSsim, + suppressAnalysisExceptions, + inputValues); } } @@ -126,31 +148,35 @@ public class TransformerAndroidTestRunner { private final int timeoutSeconds; private final boolean calculateSsim; private final boolean suppressAnalysisExceptions; + @Nullable private final Map inputValues; private TransformerAndroidTestRunner( Context context, Transformer transformer, int timeoutSeconds, boolean calculateSsim, - boolean suppressAnalysisExceptions) { + boolean suppressAnalysisExceptions, + @Nullable Map inputValues) { this.context = context; this.transformer = transformer; this.timeoutSeconds = timeoutSeconds; this.calculateSsim = calculateSsim; this.suppressAnalysisExceptions = suppressAnalysisExceptions; + this.inputValues = inputValues; } /** * Transforms the {@code uriString}, saving a summary of the transformation to the application * cache. * - * @param testId An identifier for the test. + * @param testId A unique identifier for the transformer test run. * @param uriString The uri (as a {@link String}) of the file to transform. * @return The {@link TransformationTestResult}. * @throws Exception The cause of the transformation not completing. */ public TransformationTestResult run(String testId, String uriString) throws Exception { JSONObject resultJson = new JSONObject(); + resultJson.put("inputValues", JSONObject.wrap(inputValues)); try { TransformationTestResult transformationTestResult = runInternal(testId, uriString); resultJson.put("transformationResult", getTestResultJson(transformationTestResult)); diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/BitrateAnalysisTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/BitrateAnalysisTest.java new file mode 100644 index 0000000000..c6fd8e09f3 --- /dev/null +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/BitrateAnalysisTest.java @@ -0,0 +1,118 @@ +/* + * 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 androidx.media3.transformer.mh.analysis; + +import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR; +import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR; + +import android.content.Context; +import androidx.media3.common.util.Assertions; +import androidx.media3.transformer.AndroidTestUtil; +import androidx.media3.transformer.DefaultEncoderFactory; +import androidx.media3.transformer.EncoderSelector; +import androidx.media3.transformer.Transformer; +import androidx.media3.transformer.TransformerAndroidTestRunner; +import androidx.media3.transformer.VideoEncoderSettings; +import androidx.test.core.app.ApplicationProvider; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +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 analysing output bitrate and quality for a given input bitrate. */ +@RunWith(Parameterized.class) +public class BitrateAnalysisTest { + 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 INPUT_BITRATE_MODES = + ImmutableList.of(BITRATE_MODE_VBR, BITRATE_MODE_CBR); + + private static final int START_BITRATE = 2_000_000; + private static final int END_BITRATE = 10_000_000; + private static final int BITRATE_INTERVAL = 1_000_000; + + @Parameter(0) + public int bitrate; + + @Parameter(1) + public int bitrateMode; + + @Parameter(2) + public @MonotonicNonNull String fileUri; + + @Parameters(name = "analyzeBitrate_{0}_{1}_{2}") + public static List parameters() { + List parameterList = new ArrayList<>(); + for (int bitrate = START_BITRATE; bitrate <= END_BITRATE; bitrate += BITRATE_INTERVAL) { + for (int mode : INPUT_BITRATE_MODES) { + for (String file : INPUT_FILES) { + parameterList.add(new Object[] {bitrate, mode, file}); + } + } + } + + return parameterList; + } + + @Test + public void analyzeBitrate() throws Exception { + Assertions.checkNotNull(fileUri); + String fileName = Assertions.checkNotNull(Iterables.getLast(Splitter.on("/").split(fileUri))); + String testId = String.format("analyzeBitrate_ssim_%s_%d_%s", bitrate, bitrateMode, fileName); + + Map inputValues = new HashMap<>(); + inputValues.put("targetBitrate", bitrate); + inputValues.put("inputFilename", fileName); + if (bitrateMode == BITRATE_MODE_CBR) { + inputValues.put("bitrateMode", "CBR"); + } else if (bitrateMode == BITRATE_MODE_VBR) { + inputValues.put("bitrateMode", "VBR"); + } + + Context context = ApplicationProvider.getApplicationContext(); + Transformer transformer = + new Transformer.Builder(context) + .setRemoveAudio(true) + .setEncoderFactory( + new DefaultEncoderFactory( + EncoderSelector.DEFAULT, + new VideoEncoderSettings.Builder() + .setBitrate(bitrate) + .setBitrateMode(bitrateMode) + .build(), + /* enableFallback= */ false)) + .build(); + + inputValues.put("Transformer", transformer); + + new TransformerAndroidTestRunner.Builder(context, transformer) + .setInputValues(inputValues) + .setCalculateSsim(true) + .build() + .run(testId, fileUri); + } +} diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/package-info.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/package-info.java new file mode 100644 index 0000000000..f624be94c1 --- /dev/null +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NonNullApi +package androidx.media3.transformer.mh.analysis; + +import androidx.media3.common.util.NonNullApi;