mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Speed up image to video Export
Only sample from input bitmap when the input image has changed. Introduce GainmapShaderProgram.newImmutableBitmap API that signals input bitmap changes to GainmapShaderProgram (DefaultShaderProgram). PiperOrigin-RevId: 637920207
This commit is contained in:
parent
3d8b5811b4
commit
02df88e5d9
@ -47,7 +47,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
private final Queue<BitmapFrameSequenceInfo> pendingBitmaps;
|
||||
private final GlObjectsProvider glObjectsProvider;
|
||||
|
||||
private @MonotonicNonNull GainmapShaderProgram gainmapShaderProgram;
|
||||
private @MonotonicNonNull RepeatingGainmapShaderProgram repeatingGainmapShaderProgram;
|
||||
@Nullable private GlTextureInfo currentSdrGlTextureInfo;
|
||||
private int downstreamShaderProgramCapacity;
|
||||
private boolean currentInputStreamEnded;
|
||||
@ -71,13 +71,13 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>{@link GlShaderProgram} must be a {@link GainmapShaderProgram}.
|
||||
* <p>{@link GlShaderProgram} must be a {@link RepeatingGainmapShaderProgram}.
|
||||
*/
|
||||
@Override
|
||||
public void setSamplingGlShaderProgram(GlShaderProgram samplingGlShaderProgram) {
|
||||
checkState(samplingGlShaderProgram instanceof GainmapShaderProgram);
|
||||
checkState(samplingGlShaderProgram instanceof RepeatingGainmapShaderProgram);
|
||||
downstreamShaderProgramCapacity = 0;
|
||||
this.gainmapShaderProgram = (GainmapShaderProgram) samplingGlShaderProgram;
|
||||
this.repeatingGainmapShaderProgram = (RepeatingGainmapShaderProgram) samplingGlShaderProgram;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -110,7 +110,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
videoFrameProcessingTaskExecutor.submit(
|
||||
() -> {
|
||||
if (pendingBitmaps.isEmpty()) {
|
||||
checkNotNull(gainmapShaderProgram).signalEndOfCurrentInputStream();
|
||||
checkNotNull(repeatingGainmapShaderProgram).signalEndOfCurrentInputStream();
|
||||
DebugTraceUtil.logEvent(
|
||||
COMPONENT_BITMAP_TEXTURE_MANAGER, EVENT_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
||||
} else {
|
||||
@ -155,7 +155,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
}
|
||||
|
||||
downstreamShaderProgramCapacity--;
|
||||
checkNotNull(gainmapShaderProgram)
|
||||
checkNotNull(repeatingGainmapShaderProgram)
|
||||
.queueInputFrame(
|
||||
glObjectsProvider, checkNotNull(currentSdrGlTextureInfo), currentPresentationTimeUs);
|
||||
DebugTraceUtil.logEvent(
|
||||
@ -172,7 +172,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
finishedBitmapInfo.bitmap.recycle();
|
||||
if (pendingBitmaps.isEmpty() && currentInputStreamEnded) {
|
||||
// Only signal end of stream after all pending bitmaps are processed.
|
||||
checkNotNull(gainmapShaderProgram).signalEndOfCurrentInputStream();
|
||||
checkNotNull(repeatingGainmapShaderProgram).signalEndOfCurrentInputStream();
|
||||
DebugTraceUtil.logEvent(
|
||||
COMPONENT_BITMAP_TEXTURE_MANAGER, EVENT_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
||||
currentInputStreamEnded = false;
|
||||
@ -213,8 +213,9 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
frameInfo.width,
|
||||
frameInfo.height);
|
||||
if (Util.SDK_INT >= 34 && bitmap.hasGainmap()) {
|
||||
checkNotNull(gainmapShaderProgram).setGainmap(checkNotNull(bitmap.getGainmap()));
|
||||
checkNotNull(repeatingGainmapShaderProgram).setGainmap(checkNotNull(bitmap.getGainmap()));
|
||||
}
|
||||
checkNotNull(repeatingGainmapShaderProgram).signalNewRepeatingFrameSequence();
|
||||
} catch (GlUtil.GlException e) {
|
||||
throw VideoFrameProcessingException.from(e);
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
*/
|
||||
@SuppressWarnings("FunctionalInterfaceClash") // b/228192298
|
||||
/* package */ final class DefaultShaderProgram extends BaseGlShaderProgram
|
||||
implements ExternalShaderProgram, GainmapShaderProgram {
|
||||
implements ExternalShaderProgram, RepeatingGainmapShaderProgram {
|
||||
|
||||
private static final String VERTEX_SHADER_TRANSFORMATION_PATH =
|
||||
"shaders/vertex_shader_transformation_es2.glsl";
|
||||
@ -153,6 +153,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private @MonotonicNonNull Gainmap lastGainmap;
|
||||
private int gainmapTexId;
|
||||
private @C.ColorTransfer int outputColorTransfer;
|
||||
private boolean shouldRepeatLastFrame;
|
||||
private boolean isRepeatingFrameDrawn;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -501,12 +503,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@Override
|
||||
public void drawFrame(int inputTexId, long presentationTimeUs)
|
||||
throws VideoFrameProcessingException {
|
||||
updateCompositeRgbMatrixArray(presentationTimeUs);
|
||||
boolean compositeRgbMatrixArrayChanged = updateCompositeRgbMatrixArray(presentationTimeUs);
|
||||
boolean compositeTransformationMatrixAndVisiblePolygonChanged =
|
||||
updateCompositeTransformationMatrixAndVisiblePolygon(presentationTimeUs);
|
||||
boolean uniformsChanged =
|
||||
compositeRgbMatrixArrayChanged || compositeTransformationMatrixAndVisiblePolygonChanged;
|
||||
if (visiblePolygon.size() < 3) {
|
||||
return; // Need at least three visible vertices for a triangle.
|
||||
}
|
||||
|
||||
if (shouldRepeatLastFrame && !uniformsChanged && isRepeatingFrameDrawn) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
glProgram.use();
|
||||
setGainmapSamplerAndUniforms();
|
||||
@ -524,6 +532,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
} catch (GlUtil.GlException e) {
|
||||
throw new VideoFrameProcessingException(e, presentationTimeUs);
|
||||
}
|
||||
isRepeatingFrameDrawn = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -553,6 +562,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
if (lastGainmap != null && GainmapUtil.equals(this.lastGainmap, gainmap)) {
|
||||
return;
|
||||
}
|
||||
isRepeatingFrameDrawn = false;
|
||||
this.lastGainmap = gainmap;
|
||||
if (gainmapTexId == C.INDEX_UNSET) {
|
||||
gainmapTexId = GlUtil.createTexture(gainmap.getGainmapContents());
|
||||
@ -561,6 +571,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signalNewRepeatingFrameSequence() {
|
||||
// Skipping drawFrame() is only allowed if there's only one possible output texture.
|
||||
checkState(outputTexturePool.capacity() == 1);
|
||||
shouldRepeatLastFrame = true;
|
||||
isRepeatingFrameDrawn = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldClearTextureBuffer() {
|
||||
return !(isRepeatingFrameDrawn && shouldRepeatLastFrame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the output {@link C.ColorTransfer}.
|
||||
*
|
||||
@ -581,8 +604,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
/**
|
||||
* Updates {@link #compositeTransformationMatrixArray} and {@link #visiblePolygon} based on the
|
||||
* given frame timestamp.
|
||||
*
|
||||
* <p>Returns whether the transformation matrix or visible polygon has changed.
|
||||
*/
|
||||
private void updateCompositeTransformationMatrixAndVisiblePolygon(long presentationTimeUs) {
|
||||
private boolean updateCompositeTransformationMatrixAndVisiblePolygon(long presentationTimeUs) {
|
||||
float[][] matricesAtPresentationTime = new float[matrixTransformations.size()][16];
|
||||
for (int i = 0; i < matrixTransformations.size(); i++) {
|
||||
matricesAtPresentationTime[i] =
|
||||
@ -590,7 +615,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
if (!updateMatrixCache(transformationMatrixCache, matricesAtPresentationTime)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute the compositeTransformationMatrix and transform and clip the visiblePolygon for each
|
||||
@ -616,7 +641,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
MatrixUtils.transformPoints(transformationMatrix, visiblePolygon));
|
||||
if (visiblePolygon.size() < 3) {
|
||||
// Can ignore remaining matrices as there are not enough vertices left to form a polygon.
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Calculate the input frame vertices corresponding to the output frame's visible polygon.
|
||||
@ -626,17 +651,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
compositeTransformationMatrixArray,
|
||||
/* mOffset= */ 0);
|
||||
visiblePolygon = MatrixUtils.transformPoints(tempResultMatrix, visiblePolygon);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Updates {@link #compositeRgbMatrixArray} based on the given frame timestamp. */
|
||||
private void updateCompositeRgbMatrixArray(long presentationTimeUs) {
|
||||
/**
|
||||
* Updates {@link #compositeRgbMatrixArray} based on the given frame timestamp.
|
||||
*
|
||||
* <p>Returns whether the {@link #compositeRgbMatrixArray} has changed.
|
||||
*/
|
||||
private boolean updateCompositeRgbMatrixArray(long presentationTimeUs) {
|
||||
float[][] matricesCurrTimestamp = new float[rgbMatrices.size()][16];
|
||||
for (int i = 0; i < rgbMatrices.size(); i++) {
|
||||
matricesCurrTimestamp[i] = rgbMatrices.get(i).getMatrix(presentationTimeUs, useHdr);
|
||||
}
|
||||
|
||||
if (!updateMatrixCache(rgbMatrixCache, matricesCurrTimestamp)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
GlUtil.setToIdentity(compositeRgbMatrixArray);
|
||||
@ -656,6 +686,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
/* destPost= */ 0,
|
||||
/* length= */ tempResultMatrix.length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2024 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
|
||||
*
|
||||
* https://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.effect;
|
||||
|
||||
/** Interface for a {@link GlShaderProgram} that can repeat an input frame. */
|
||||
/* package */ interface RepeatingFrameShaderProgram extends GlShaderProgram {
|
||||
|
||||
/**
|
||||
* Signals that the frame contents will change in the next call to {@link
|
||||
* GlShaderProgram#queueInputFrame}.
|
||||
*
|
||||
* <p>This class can assume that the input frame contents are unchanged until the next call to
|
||||
* this method.
|
||||
*/
|
||||
void signalNewRepeatingFrameSequence();
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2024 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
|
||||
*
|
||||
* https://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.effect;
|
||||
|
||||
/* package */ interface RepeatingGainmapShaderProgram
|
||||
extends RepeatingFrameShaderProgram, GainmapShaderProgram {}
|
Binary file not shown.
After Width: | Height: | Size: 834 KiB |
@ -138,17 +138,17 @@ public final class SequenceEffectTestUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the first frame extracted from the video in filePath matches output in {@link
|
||||
* #PNG_ASSET_BASE_PATH}/{@code testId}_0.png.
|
||||
* Asserts that the first {@code frameCount} frames extracted from the video in {@code filePath}
|
||||
* match the expected output in {@link #PNG_ASSET_BASE_PATH}/{@code testId}_num.png.
|
||||
*
|
||||
* <p>Also saves the first frame as a bitmap, in case they differ from expected.
|
||||
*/
|
||||
public static void assertFirstFrameMatchesExpectedPsnrAndSave(
|
||||
Context context, String testId, String filePath, float psnrThreshold)
|
||||
public static void assertFramesMatchExpectedPsnrAndSave(
|
||||
Context context, String testId, String filePath, float psnrThreshold, int frameCount)
|
||||
throws IOException, InterruptedException {
|
||||
Bitmap firstEncodedFrame = extractBitmapsFromVideo(context, filePath).get(0);
|
||||
assertBitmapsMatchExpectedPsnrAndSave(
|
||||
ImmutableList.of(firstEncodedFrame), testId, psnrThreshold);
|
||||
ImmutableList<Bitmap> frames =
|
||||
extractBitmapsFromVideo(context, filePath).subList(0, frameCount);
|
||||
assertBitmapsMatchExpectedPsnrAndSave(frames, testId, psnrThreshold);
|
||||
}
|
||||
|
||||
private static void assertBitmapsMatchExpectedPsnrAndSave(
|
||||
|
@ -43,7 +43,7 @@ import static androidx.media3.transformer.SequenceEffectTestUtil.PSNR_THRESHOLD;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.PSNR_THRESHOLD_HD;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.SINGLE_30_FPS_VIDEO_FRAME_THRESHOLD_MS;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.assertBitmapsMatchExpectedAndSave;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.assertFirstFrameMatchesExpectedPsnrAndSave;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.assertFramesMatchExpectedPsnrAndSave;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.clippedVideo;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.createComposition;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.decoderProducesWashedOutColours;
|
||||
@ -175,8 +175,8 @@ public final class TransformerSequenceEffectTest {
|
||||
atLeastOneDecoderSucceeds = true;
|
||||
|
||||
assertThat(new File(result.filePath).length()).isGreaterThan(0);
|
||||
assertFirstFrameMatchesExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD);
|
||||
assertFramesMatchExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD, /* frameCount= */ 1);
|
||||
}
|
||||
assertThat(atLeastOneDecoderSucceeds).isTrue();
|
||||
}
|
||||
@ -215,8 +215,8 @@ public final class TransformerSequenceEffectTest {
|
||||
atLeastOneDecoderSucceeds = true;
|
||||
|
||||
assertThat(new File(result.filePath).length()).isGreaterThan(0);
|
||||
assertFirstFrameMatchesExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD);
|
||||
assertFramesMatchExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD, /* frameCount= */ 1);
|
||||
}
|
||||
assertThat(atLeastOneDecoderSucceeds).isTrue();
|
||||
}
|
||||
@ -253,8 +253,8 @@ public final class TransformerSequenceEffectTest {
|
||||
atLeastOneDecoderSucceeds = true;
|
||||
|
||||
assertThat(new File(result.filePath).length()).isGreaterThan(0);
|
||||
assertFirstFrameMatchesExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD);
|
||||
assertFramesMatchExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD, /* frameCount= */ 1);
|
||||
}
|
||||
assertThat(atLeastOneDecoderSucceeds).isTrue();
|
||||
}
|
||||
@ -294,8 +294,8 @@ public final class TransformerSequenceEffectTest {
|
||||
atLeastOneDecoderSucceeds = true;
|
||||
|
||||
assertThat(new File(result.filePath).length()).isGreaterThan(0);
|
||||
assertFirstFrameMatchesExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD);
|
||||
assertFramesMatchExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD, /* frameCount= */ 1);
|
||||
}
|
||||
assertThat(atLeastOneDecoderSucceeds).isTrue();
|
||||
}
|
||||
@ -336,8 +336,8 @@ public final class TransformerSequenceEffectTest {
|
||||
atLeastOneDecoderSucceeds = true;
|
||||
|
||||
assertThat(new File(result.filePath).length()).isGreaterThan(0);
|
||||
assertFirstFrameMatchesExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD);
|
||||
assertFramesMatchExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD, /* frameCount= */ 1);
|
||||
}
|
||||
assertThat(atLeastOneDecoderSucceeds).isTrue();
|
||||
|
||||
@ -390,12 +390,15 @@ public final class TransformerSequenceEffectTest {
|
||||
|
||||
assertThat(new File(result.filePath).length()).isGreaterThan(0);
|
||||
// The PSNR threshold was chosen based on:
|
||||
// Pixel 8 with coordinate rounding error during texture sampling, hits PSNR 23.4. With fix ->
|
||||
// 29.5
|
||||
// Realmi C11 with bug fix hits PSNR 29.94
|
||||
// rmx3563 -> 28.8
|
||||
assertFirstFrameMatchesExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), 28.5f);
|
||||
// Pixel 8 with coordinate rounding error during texture sampling, gets PSNR 23.4.
|
||||
// After fix -> 29.5
|
||||
// rmx3563 with bug fix achieves PSNR 28.8
|
||||
assertFramesMatchExpectedPsnrAndSave(
|
||||
context,
|
||||
testId,
|
||||
checkNotNull(result.filePath),
|
||||
/* psnrThreshold= */ 28.5f,
|
||||
/* frameCount= */ 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -15,21 +15,30 @@
|
||||
*/
|
||||
package androidx.media3.transformer.mh;
|
||||
|
||||
import static androidx.media3.common.MimeTypes.VIDEO_H264;
|
||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_LONG_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING;
|
||||
import static androidx.media3.transformer.AndroidTestUtil.ULTRA_HDR_URI_STRING;
|
||||
import static androidx.media3.transformer.AndroidTestUtil.assumeFormatsSupported;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.effect.Presentation;
|
||||
import androidx.media3.transformer.AndroidTestUtil;
|
||||
import androidx.media3.transformer.EditedMediaItem;
|
||||
import androidx.media3.transformer.Effects;
|
||||
import androidx.media3.transformer.ExportTestResult;
|
||||
import androidx.media3.transformer.Transformer;
|
||||
import androidx.media3.transformer.TransformerAndroidTestRunner;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@ -77,4 +86,53 @@ public class TranscodeSpeedTest {
|
||||
|
||||
assertThat(result.throughputFps).isAtLeast(20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportImage_to720p_completesWithHighThroughput() throws Exception {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
Format outputFormat =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(VIDEO_H264)
|
||||
.setFrameRate(30.00f)
|
||||
.setCodecs("avc1.42C028")
|
||||
.setWidth(1280)
|
||||
.setHeight(720)
|
||||
.build();
|
||||
assumeFormatsSupported(
|
||||
context,
|
||||
testId,
|
||||
/* inputFormat= */ AndroidTestUtil.MP4_LONG_ASSET_WITH_INCREASING_TIMESTAMPS_FORMAT,
|
||||
outputFormat);
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context).setVideoMimeType(MimeTypes.VIDEO_H264).build();
|
||||
boolean isHighPerformance = Util.SDK_INT >= 31 && Build.SOC_MODEL.startsWith("Tensor");
|
||||
if (Util.SDK_INT == 33 && Ascii.toLowerCase(Util.MODEL).contains("pixel 6")) {
|
||||
// Pixel 6 is usually quick, unless it's on API 33.
|
||||
isHighPerformance = false;
|
||||
}
|
||||
// This test uses ULTRA_HDR_URI_STRING because it's high resolution.
|
||||
// Ultra HDR gainmap is ignored.
|
||||
EditedMediaItem editedMediaItem =
|
||||
new EditedMediaItem.Builder(MediaItem.fromUri(ULTRA_HDR_URI_STRING))
|
||||
.setFrameRate(30)
|
||||
.setDurationUs(isHighPerformance ? 45_000_000 : 15_000_000)
|
||||
.setEffects(
|
||||
new Effects(
|
||||
/* audioProcessors= */ ImmutableList.of(),
|
||||
/* videoEffects= */ ImmutableList.of(
|
||||
Presentation.createForWidthAndHeight(
|
||||
720, 1280, Presentation.LAYOUT_SCALE_TO_FIT))))
|
||||
.build();
|
||||
|
||||
ExportTestResult result =
|
||||
new TransformerAndroidTestRunner.Builder(context, transformer)
|
||||
.build()
|
||||
.run(testId, editedMediaItem);
|
||||
|
||||
// This test depends on device GPU performance. Sampling high-resolution textures
|
||||
// is expensive. If an extra shader program runs on each frame, devices with slow GPU
|
||||
// such as moto e5 play will drop to 5 fps.
|
||||
// Devices with a fast GPU and encoder will drop under 300 fps.
|
||||
assertThat(result.throughputFps).isAtLeast(isHighPerformance ? 400 : 20);
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import static androidx.media3.transformer.SequenceEffectTestUtil.NO_EFFECT;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.PSNR_THRESHOLD_HD;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.SINGLE_30_FPS_VIDEO_FRAME_THRESHOLD_MS;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.assertBitmapsMatchExpectedAndSave;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.assertFirstFrameMatchesExpectedPsnrAndSave;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.assertFramesMatchExpectedPsnrAndSave;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.clippedVideo;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.createComposition;
|
||||
import static androidx.media3.transformer.SequenceEffectTestUtil.tryToExportCompositionWithDecoder;
|
||||
@ -226,8 +226,8 @@ public final class TransformerSequenceEffectTestWithHdr {
|
||||
atLeastOneDecoderSucceeds = true;
|
||||
|
||||
assertThat(checkNotNull(result).filePath).isNotNull();
|
||||
assertFirstFrameMatchesExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD);
|
||||
assertFramesMatchExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD, /* frameCount= */ 1);
|
||||
}
|
||||
assertThat(atLeastOneDecoderSucceeds).isTrue();
|
||||
}
|
||||
@ -261,8 +261,8 @@ public final class TransformerSequenceEffectTestWithHdr {
|
||||
atLeastOneDecoderSucceeds = true;
|
||||
|
||||
assertThat(checkNotNull(result).filePath).isNotNull();
|
||||
assertFirstFrameMatchesExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD);
|
||||
assertFramesMatchExpectedPsnrAndSave(
|
||||
context, testId, checkNotNull(result.filePath), PSNR_THRESHOLD_HD, /* frameCount= */ 1);
|
||||
}
|
||||
assertThat(atLeastOneDecoderSucceeds).isTrue();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user