Use software decoder in VideoDecodingWrapper

Hardware decoder on some devices fails to write 1920x1080 YUV_420_888 buffers into an ImageReader. This change allows us to remove skipCalculateSsim device workaround in ExportTest.java.

VideoDecodingWrapper now uses media3.MediaExtractorCompat: necessary for parsing of MediaFormat#KEY_CODECS_STRING and decoder capabilities check

PiperOrigin-RevId: 648726721
This commit is contained in:
dancho 2024-07-02 08:26:33 -07:00 committed by Copybara-Service
parent 3b7d59ef4f
commit 91bf3d1da1
2 changed files with 47 additions and 31 deletions

View File

@ -23,29 +23,42 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.content.Context; import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;
import android.media.Image; import android.media.Image;
import android.media.ImageReader; import android.media.ImageReader;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaExtractor;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.MediaFormatUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.MediaExtractorCompat;
import androidx.media3.exoplayer.mediacodec.MediaCodecInfo;
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List;
/** A wrapper for decoding a video using {@link MediaCodec}. */ /**
* A wrapper for decoding a video using {@link MediaCodec}.
*
* <p>This test utility class prefers using a software decoder. Depending on video resolution and
* device, some hardware decoders fail to write frames in {@link ImageFormat#YUV_420_888} to {@link
* ImageReader} for use in CPU test utility functions.
*/
@UnstableApi @UnstableApi
@RequiresApi(21) @RequiresApi(21)
public final class VideoDecodingWrapper implements AutoCloseable { public final class VideoDecodingWrapper implements AutoCloseable {
private static final String TAG = "VideoDecodingWrapper";
private static final int IMAGE_AVAILABLE_TIMEOUT_MS = 10_000; private static final int IMAGE_AVAILABLE_TIMEOUT_MS = 10_000;
// Use ExoPlayer's 10ms timeout setting. In practise, the test durations from using timeouts of // Use ExoPlayer's 10ms timeout setting. In practise, the test durations from using timeouts of
@ -53,13 +66,11 @@ public final class VideoDecodingWrapper implements AutoCloseable {
private static final long DEQUEUE_TIMEOUT_US = 10_000; private static final long DEQUEUE_TIMEOUT_US = 10_000;
// SSIM should be calculated using the luma (Y') channel, thus using the YUV color space. // SSIM should be calculated using the luma (Y') channel, thus using the YUV color space.
private static final int IMAGE_READER_COLOR_SPACE = ImageFormat.YUV_420_888; private static final int IMAGE_READER_COLOR_SPACE = ImageFormat.YUV_420_888;
private static final int MEDIA_CODEC_COLOR_SPACE = private static final int MEDIA_CODEC_COLOR_SPACE = CodecCapabilities.COLOR_FormatYUV420Flexible;
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
private static final String ASSET_FILE_SCHEME = "asset:///";
private final MediaFormat mediaFormat; private final MediaFormat mediaFormat;
private final MediaCodec mediaCodec; private final MediaCodec mediaCodec;
private final MediaExtractor mediaExtractor; private final MediaExtractorCompat mediaExtractor;
private final MediaCodec.BufferInfo bufferInfo; private final MediaCodec.BufferInfo bufferInfo;
private final ImageReader imageReader; private final ImageReader imageReader;
private final ConditionVariable imageAvailableConditionVariable; private final ConditionVariable imageAvailableConditionVariable;
@ -85,17 +96,9 @@ public final class VideoDecodingWrapper implements AutoCloseable {
Context context, String filePath, int comparisonInterval, int maxImagesAllowed) Context context, String filePath, int comparisonInterval, int maxImagesAllowed)
throws IOException { throws IOException {
this.comparisonInterval = comparisonInterval; this.comparisonInterval = comparisonInterval;
mediaExtractor = new MediaExtractor(); mediaExtractor = new MediaExtractorCompat(context);
bufferInfo = new MediaCodec.BufferInfo(); bufferInfo = new MediaCodec.BufferInfo();
mediaExtractor.setDataSource(Uri.parse(filePath), 0);
if (filePath.contains(ASSET_FILE_SCHEME)) {
AssetFileDescriptor assetFd =
context.getAssets().openFd(filePath.replace(ASSET_FILE_SCHEME, ""));
mediaExtractor.setDataSource(
assetFd.getFileDescriptor(), assetFd.getStartOffset(), assetFd.getLength());
} else {
mediaExtractor.setDataSource(filePath);
}
@Nullable MediaFormat mediaFormat = null; @Nullable MediaFormat mediaFormat = null;
for (int i = 0; i < mediaExtractor.getTrackCount(); i++) { for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
@ -125,7 +128,29 @@ public final class VideoDecodingWrapper implements AutoCloseable {
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MEDIA_CODEC_COLOR_SPACE); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MEDIA_CODEC_COLOR_SPACE);
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, MEDIA_CODEC_PRIORITY_NON_REALTIME); mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, MEDIA_CODEC_PRIORITY_NON_REALTIME);
this.mediaFormat = mediaFormat; this.mediaFormat = mediaFormat;
mediaCodec = MediaCodec.createDecoderByType(sampleMimeType);
// Try to find a software MediaCodec that supports the video dimensions, with fall back to
// MediaCodec.createDecoderByType.
MediaCodec softwareMediaCodec = null;
try {
List<MediaCodecInfo> codecInfos =
MediaCodecUtil.getDecoderInfos(
sampleMimeType, /* secure= */ false, /* tunneling= */ false);
Format format = MediaFormatUtil.createFormatFromMediaFormat(mediaFormat);
for (MediaCodecInfo codecInfo : codecInfos) {
if (!codecInfo.hardwareAccelerated && codecInfo.isFormatSupported(format)) {
softwareMediaCodec = MediaCodec.createByCodecName(codecInfo.name);
break;
}
}
} catch (MediaCodecUtil.DecoderQueryException exception) {
Log.e(TAG, "Failed to find software decoder: " + exception);
}
if (softwareMediaCodec == null) {
mediaCodec = MediaCodec.createDecoderByType(sampleMimeType);
} else {
mediaCodec = softwareMediaCodec;
}
} }
/** /**

View File

@ -95,13 +95,10 @@ public class ExportTest {
.setEncoderFactory(new ForceEncodeEncoderFactory(context)) .setEncoderFactory(new ForceEncodeEncoderFactory(context))
.build(); .build();
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_WITH_INCREASING_TIMESTAMPS.uri)); MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_WITH_INCREASING_TIMESTAMPS.uri));
boolean skipCalculateSsim =
(Util.SDK_INT < 33 && (Util.MODEL.equals("SM-F711U1") || Util.MODEL.equals("SM-F926U1")))
|| (Util.SDK_INT == 33 && Util.MODEL.equals("LE2121"));
ExportTestResult result = ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)
.setRequestCalculateSsim(!skipCalculateSsim) .setRequestCalculateSsim(true)
.build() .build()
.run(testId, mediaItem); .run(testId, mediaItem);
@ -143,13 +140,10 @@ public class ExportTest {
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_WITH_INCREASING_TIMESTAMPS.uri)); MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_WITH_INCREASING_TIMESTAMPS.uri));
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build(); new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
boolean skipCalculateSsim =
(Util.SDK_INT < 33 && (Util.MODEL.equals("SM-F711U1") || Util.MODEL.equals("SM-F926U1")))
|| (Util.SDK_INT == 33 && Util.MODEL.equals("LE2121"));
ExportTestResult result = ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)
.setRequestCalculateSsim(!skipCalculateSsim) .setRequestCalculateSsim(true)
.build() .build()
.run(testId, editedMediaItem); .run(testId, editedMediaItem);
@ -271,13 +265,10 @@ public class ExportTest {
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_WITH_INCREASING_TIMESTAMPS.uri)); MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_WITH_INCREASING_TIMESTAMPS.uri));
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build(); new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
boolean skipCalculateSsim =
(Util.SDK_INT < 33 && (Util.MODEL.equals("SM-F711U1") || Util.MODEL.equals("SM-F926U1")))
|| (Util.SDK_INT == 33 && Util.MODEL.equals("LE2121"));
ExportTestResult result = ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)
.setRequestCalculateSsim(!skipCalculateSsim) .setRequestCalculateSsim(true)
.build() .build()
.run(testId, editedMediaItem); .run(testId, editedMediaItem);