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:
parent
fb7cf154de
commit
c2fb2f1520
Binary file not shown.
@ -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";
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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),
|
||||
|
Loading…
x
Reference in New Issue
Block a user