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")
|
.setCodecs("avc1.42C033")
|
||||||
.build();
|
.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. */
|
/** 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 =
|
public static final String MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S_URI_STRING =
|
||||||
"asset:///media/mp4/sample_with_increasing_timestamps_320w_240h.mp4";
|
"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.MediaCodecInfo;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
|
||||||
|
import com.google.common.base.Ascii;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -189,7 +190,8 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
|||||||
@Override
|
@Override
|
||||||
public DefaultCodec createForAudioDecoding(Format format) throws ExportException {
|
public DefaultCodec createForAudioDecoding(Format format) throws ExportException {
|
||||||
MediaFormat mediaFormat = createMediaFormatFromFormat(format);
|
MediaFormat mediaFormat = createMediaFormatFromFormat(format);
|
||||||
return createCodecForMediaFormat(mediaFormat, format, /* outputSurface= */ null);
|
return createCodecForMediaFormat(
|
||||||
|
mediaFormat, format, /* outputSurface= */ null, /* devicePrefersSoftwareDecoder= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
@ -244,11 +246,15 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
|||||||
mediaFormat.setInteger("importance", max(0, -codecPriority));
|
mediaFormat.setInteger("importance", max(0, -codecPriority));
|
||||||
}
|
}
|
||||||
|
|
||||||
return createCodecForMediaFormat(mediaFormat, format, outputSurface);
|
return createCodecForMediaFormat(
|
||||||
|
mediaFormat, format, outputSurface, devicePrefersSoftwareDecoder(format));
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultCodec createCodecForMediaFormat(
|
private DefaultCodec createCodecForMediaFormat(
|
||||||
MediaFormat mediaFormat, Format format, @Nullable Surface outputSurface)
|
MediaFormat mediaFormat,
|
||||||
|
Format format,
|
||||||
|
@Nullable Surface outputSurface,
|
||||||
|
boolean devicePrefersSoftwareDecoder)
|
||||||
throws ExportException {
|
throws ExportException {
|
||||||
List<MediaCodecInfo> decoderInfos = ImmutableList.of();
|
List<MediaCodecInfo> decoderInfos = ImmutableList.of();
|
||||||
checkNotNull(format.sampleMimeType);
|
checkNotNull(format.sampleMimeType);
|
||||||
@ -265,10 +271,21 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
|||||||
Log.e(TAG, "Error querying decoders", e);
|
Log.e(TAG, "Error querying decoders", e);
|
||||||
throw createExportException(format, /* reason= */ "Querying codecs failed");
|
throw createExportException(format, /* reason= */ "Querying codecs failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decoderInfos.isEmpty()) {
|
if (decoderInfos.isEmpty()) {
|
||||||
throw createExportException(format, /* reason= */ "No decoders for format");
|
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<>();
|
List<ExportException> codecInitExceptions = new ArrayList<>();
|
||||||
DefaultCodec codec =
|
DefaultCodec codec =
|
||||||
@ -354,6 +371,20 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
|||||||
return SDK_INT >= 29 && context.getApplicationInfo().targetSdkVersion >= 29;
|
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) {
|
private static ExportException createExportException(Format format, String reason) {
|
||||||
return ExportException.createForCodec(
|
return ExportException.createForCodec(
|
||||||
new IllegalArgumentException(reason),
|
new IllegalArgumentException(reason),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user