Work around 1080p export failures on certain devices

Fall back to using software decoder for 1920x1080 for certain
devices.

PiperOrigin-RevId: 636132298
This commit is contained in:
dancho 2024-05-22 05:35:50 -07:00 committed by Copybara-Service
parent fb7cf154de
commit c2fb2f1520
4 changed files with 126 additions and 4 deletions

View File

@ -160,6 +160,17 @@ public final class AndroidTestUtil {
.setCodecs("avc1.42C033")
.build();
public static final String MP4_LONG_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING =
"asset:///media/mp4/long_1080p_videoonly_lowbitrate.mp4";
public static final Format MP4_LONG_ASSET_WITH_INCREASING_TIMESTAMPS_FORMAT =
new Format.Builder()
.setSampleMimeType(VIDEO_H264)
.setWidth(1920)
.setHeight(1080)
.setFrameRate(30.00f)
.setCodecs("avc1.42C028")
.build();
/** Baseline profile level 3.0 H.264 stream, which should be supported on all devices. */
public static final String MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S_URI_STRING =
"asset:///media/mp4/sample_with_increasing_timestamps_320w_240h.mp4";

View File

@ -0,0 +1,80 @@
/*
* 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.transformer.mh;
import static androidx.media3.transformer.AndroidTestUtil.MP4_LONG_ASSET_WITH_INCREASING_TIMESTAMPS_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 androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.transformer.AndroidTestUtil;
import androidx.media3.transformer.EditedMediaItem;
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 org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
/** Checks transcoding speed. */
@RunWith(AndroidJUnit4.class)
public class TranscodeSpeedTest {
@Rule public final TestName testName = new TestName();
private String testId;
@Before
public void setUpTestId() {
testId = testName.getMethodName();
}
@Test
public void export1920x1080_to1080p_completesWithAtLeast20Fps() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
assumeFormatsSupported(
context,
testId,
/* inputFormat= */ AndroidTestUtil.MP4_LONG_ASSET_WITH_INCREASING_TIMESTAMPS_FORMAT,
/* outputFormat= */ AndroidTestUtil.MP4_LONG_ASSET_WITH_INCREASING_TIMESTAMPS_FORMAT);
Transformer transformer =
new Transformer.Builder(context)
.setVideoMimeType(MimeTypes.VIDEO_H264)
.setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory(context))
.build();
MediaItem mediaItem =
MediaItem.fromUri(Uri.parse(MP4_LONG_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING))
.buildUpon()
.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder().setEndPositionMs(15_000).build())
.build();
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
assertThat(result.throughputFps).isAtLeast(20);
}
}

View File

@ -41,6 +41,7 @@ import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.mediacodec.MediaCodecInfo;
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
@ -189,7 +190,8 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
@Override
public DefaultCodec createForAudioDecoding(Format format) throws ExportException {
MediaFormat mediaFormat = createMediaFormatFromFormat(format);
return createCodecForMediaFormat(mediaFormat, format, /* outputSurface= */ null);
return createCodecForMediaFormat(
mediaFormat, format, /* outputSurface= */ null, /* devicePrefersSoftwareDecoder= */ false);
}
@SuppressLint("InlinedApi")
@ -244,11 +246,15 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
mediaFormat.setInteger("importance", max(0, -codecPriority));
}
return createCodecForMediaFormat(mediaFormat, format, outputSurface);
return createCodecForMediaFormat(
mediaFormat, format, outputSurface, devicePrefersSoftwareDecoder(format));
}
private DefaultCodec createCodecForMediaFormat(
MediaFormat mediaFormat, Format format, @Nullable Surface outputSurface)
MediaFormat mediaFormat,
Format format,
@Nullable Surface outputSurface,
boolean devicePrefersSoftwareDecoder)
throws ExportException {
List<MediaCodecInfo> decoderInfos = ImmutableList.of();
checkNotNull(format.sampleMimeType);
@ -265,10 +271,21 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
Log.e(TAG, "Error querying decoders", e);
throw createExportException(format, /* reason= */ "Querying codecs failed");
}
if (decoderInfos.isEmpty()) {
throw createExportException(format, /* reason= */ "No decoders for format");
}
if (devicePrefersSoftwareDecoder) {
List<MediaCodecInfo> softwareDecoderInfos = new ArrayList<>();
for (int i = 0; i < decoderInfos.size(); ++i) {
MediaCodecInfo mediaCodecInfo = decoderInfos.get(i);
if (!mediaCodecInfo.hardwareAccelerated) {
softwareDecoderInfos.add(mediaCodecInfo);
}
}
if (!softwareDecoderInfos.isEmpty()) {
decoderInfos = softwareDecoderInfos;
}
}
List<ExportException> codecInitExceptions = new ArrayList<>();
DefaultCodec codec =
@ -354,6 +371,20 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
return SDK_INT >= 29 && context.getApplicationInfo().targetSdkVersion >= 29;
}
private static boolean devicePrefersSoftwareDecoder(Format format) {
// TODO: b/255953153 - Capture this corner case with refactored fallback API.
// Some devices fail to configure a 1080p hardware encoder when a 1080p hardware decoder
// was created. Fall back to using a software decoder (see b/283768701).
// During a 1080p -> 180p export, using the hardware decoder would be faster than software
// decoder (68 fps vs 45 fps).
// When transcoding 1080p to 1080p, software decoder + hardware encoder (33 fps) outperforms
// hardware decoder + software encoder (17 fps).
// Due to b/267740292 using hardware to software encoder fallback is risky.
return format.width * format.height >= 1920 * 1080
&& (Ascii.equalsIgnoreCase(Util.MODEL, "vivo 1906")
|| Ascii.equalsIgnoreCase(Util.MODEL, "redmi 8"));
}
private static ExportException createExportException(Format format, String reason) {
return ExportException.createForCodec(
new IllegalArgumentException(reason),