HDR: Assert output C.ColorTransfer for tests.
To confirm that tone mapping did or did not happen. PiperOrigin-RevId: 476354606
This commit is contained in:
parent
dc9fa4f463
commit
87fd51a39b
@ -18,7 +18,6 @@ package androidx.media3.effect;
|
|||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
import static androidx.media3.effect.BitmapTestUtil.MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE;
|
import static androidx.media3.effect.BitmapTestUtil.MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE;
|
||||||
import static androidx.media3.effect.FrameProcessorTestUtil.decodeOneFrame;
|
|
||||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
@ -38,6 +37,7 @@ import androidx.media3.common.FrameInfo;
|
|||||||
import androidx.media3.common.FrameProcessingException;
|
import androidx.media3.common.FrameProcessingException;
|
||||||
import androidx.media3.common.FrameProcessor;
|
import androidx.media3.common.FrameProcessor;
|
||||||
import androidx.media3.common.SurfaceInfo;
|
import androidx.media3.common.SurfaceInfo;
|
||||||
|
import androidx.media3.test.utils.DecodeOneFrameTestUtil;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -465,11 +465,11 @@ public final class GlEffectsFrameProcessorPixelTest {
|
|||||||
DebugViewProvider.NONE,
|
DebugViewProvider.NONE,
|
||||||
ColorInfo.SDR_BT709_LIMITED,
|
ColorInfo.SDR_BT709_LIMITED,
|
||||||
/* releaseFramesAutomatically= */ true));
|
/* releaseFramesAutomatically= */ true));
|
||||||
decodeOneFrame(
|
DecodeOneFrameTestUtil.decodeOneAssetFileFrame(
|
||||||
INPUT_MP4_ASSET_STRING,
|
INPUT_MP4_ASSET_STRING,
|
||||||
new FrameProcessorTestUtil.Listener() {
|
new DecodeOneFrameTestUtil.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onVideoMediaFormatExtracted(MediaFormat mediaFormat) {
|
public void onContainerExtracted(MediaFormat mediaFormat) {
|
||||||
glEffectsFrameProcessor.setInputFrameInfo(
|
glEffectsFrameProcessor.setInputFrameInfo(
|
||||||
new FrameInfo(
|
new FrameInfo(
|
||||||
mediaFormat.getInteger(MediaFormat.KEY_WIDTH),
|
mediaFormat.getInteger(MediaFormat.KEY_WIDTH),
|
||||||
@ -480,7 +480,7 @@ public final class GlEffectsFrameProcessorPixelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoMediaFormatRead(MediaFormat mediaFormat) {
|
public void onFrameDecoded(MediaFormat mediaFormat) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package androidx.media3.effect;
|
package androidx.media3.test.utils;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
@ -28,52 +28,98 @@ import android.media.MediaCodec;
|
|||||||
import android.media.MediaExtractor;
|
import android.media.MediaExtractor;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.media3.common.FrameProcessor;
|
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
/** Utilities for instrumentation tests for {@link FrameProcessor}. */
|
/** Utilities for decoding a frame for tests. */
|
||||||
public class FrameProcessorTestUtil {
|
@UnstableApi
|
||||||
|
public class DecodeOneFrameTestUtil {
|
||||||
|
|
||||||
/** Listener for decoding events. */
|
/** Listener for decoding events. */
|
||||||
interface Listener {
|
public interface Listener {
|
||||||
/** Called when the video {@link MediaFormat} is extracted from the container. */
|
/** Called when the video {@link MediaFormat} is extracted from the container. */
|
||||||
void onVideoMediaFormatExtracted(MediaFormat mediaFormat);
|
void onContainerExtracted(MediaFormat mediaFormat);
|
||||||
|
|
||||||
/** Called when the video {@link MediaFormat} is read by the decoder from the byte stream. */
|
/**
|
||||||
void onVideoMediaFormatRead(MediaFormat mediaFormat);
|
* Called when the video {@link MediaFormat} is read by the decoder from the byte stream, after
|
||||||
|
* a frame is decoded.
|
||||||
|
*/
|
||||||
|
void onFrameDecoded(MediaFormat mediaFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Timeout for dequeueing buffers from the codec, in microseconds. */
|
/** Timeout for dequeueing buffers from the codec, in microseconds. */
|
||||||
private static final int DEQUEUE_TIMEOUT_US = 5_000_000;
|
private static final int DEQUEUE_TIMEOUT_US = 5_000_000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes one frame from the {@code assetFilePath} and renders it to the {@code surface}.
|
* Reads and decodes one frame from the {@code cacheFilePath} and renders it to the {@code
|
||||||
|
* surface}.
|
||||||
|
*
|
||||||
|
* @param cacheFilePath The path to the file in the cache directory.
|
||||||
|
* @param listener A {@link Listener} implementation.
|
||||||
|
* @param surface The {@link Surface} to render the decoded frame to, {@code null} if the decoded
|
||||||
|
* frame is not needed.
|
||||||
|
*/
|
||||||
|
public static void decodeOneCacheFileFrame(
|
||||||
|
String cacheFilePath, Listener listener, @Nullable Surface surface) throws Exception {
|
||||||
|
MediaExtractor mediaExtractor = new MediaExtractor();
|
||||||
|
try {
|
||||||
|
mediaExtractor.setDataSource(cacheFilePath);
|
||||||
|
decodeOneFrame(mediaExtractor, listener, surface);
|
||||||
|
} finally {
|
||||||
|
mediaExtractor.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and decodes one frame from the {@code assetFilePath} and renders it to the {@code
|
||||||
|
* surface}.
|
||||||
*
|
*
|
||||||
* @param assetFilePath The path to the file in the asset directory.
|
* @param assetFilePath The path to the file in the asset directory.
|
||||||
* @param listener A {@link Listener} implementation.
|
* @param listener A {@link Listener} implementation.
|
||||||
* @param surface The {@link Surface} to render the decoded frame to, {@code null} if the decoded
|
* @param surface The {@link Surface} to render the decoded frame to, {@code null} if the decoded
|
||||||
* frame is not needed.
|
* frame is not needed.
|
||||||
*/
|
*/
|
||||||
public static void decodeOneFrame(
|
public static void decodeOneAssetFileFrame(
|
||||||
String assetFilePath, Listener listener, @Nullable Surface surface) throws Exception {
|
String assetFilePath, Listener listener, @Nullable Surface surface) throws Exception {
|
||||||
|
MediaExtractor mediaExtractor = new MediaExtractor();
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
try (AssetFileDescriptor afd = context.getAssets().openFd(assetFilePath)) {
|
||||||
|
mediaExtractor.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
||||||
|
decodeOneFrame(mediaExtractor, listener, surface);
|
||||||
|
} finally {
|
||||||
|
mediaExtractor.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and decodes one frame from the {@code mediaExtractor} and renders it to the {@code
|
||||||
|
* surface}.
|
||||||
|
*
|
||||||
|
* @param mediaExtractor The {@link MediaExtractor} with a {@link
|
||||||
|
* MediaExtractor#setDataSource(String) data source set}.
|
||||||
|
* @param listener A {@link Listener} implementation.
|
||||||
|
* @param surface The {@link Surface} to render the decoded frame to, {@code null} if the decoded
|
||||||
|
* frame is not needed.
|
||||||
|
*/
|
||||||
|
private static void decodeOneFrame(
|
||||||
|
MediaExtractor mediaExtractor, Listener listener, @Nullable Surface surface)
|
||||||
|
throws Exception {
|
||||||
// Set up the extractor to read the first video frame and get its format.
|
// Set up the extractor to read the first video frame and get its format.
|
||||||
if (surface == null) {
|
if (surface == null) {
|
||||||
// Creates a placeholder surface.
|
// Creates a placeholder surface.
|
||||||
surface = new Surface(new SurfaceTexture(/* texName= */ 0));
|
surface = new Surface(new SurfaceTexture(/* texName= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaExtractor mediaExtractor = new MediaExtractor();
|
|
||||||
@Nullable MediaCodec mediaCodec = null;
|
@Nullable MediaCodec mediaCodec = null;
|
||||||
@Nullable MediaFormat mediaFormat = null;
|
@Nullable MediaFormat mediaFormat = null;
|
||||||
Context context = getApplicationContext();
|
|
||||||
try (AssetFileDescriptor afd = context.getAssets().openFd(assetFilePath)) {
|
try {
|
||||||
mediaExtractor.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
|
||||||
for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
|
for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
|
||||||
if (MimeTypes.isVideo(mediaExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME))) {
|
if (MimeTypes.isVideo(mediaExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME))) {
|
||||||
mediaFormat = mediaExtractor.getTrackFormat(i);
|
mediaFormat = mediaExtractor.getTrackFormat(i);
|
||||||
listener.onVideoMediaFormatExtracted(checkNotNull(mediaFormat));
|
listener.onContainerExtracted(checkNotNull(mediaFormat));
|
||||||
mediaExtractor.selectTrack(i);
|
mediaExtractor.selectTrack(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -113,7 +159,7 @@ public class FrameProcessorTestUtil {
|
|||||||
do {
|
do {
|
||||||
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIMEOUT_US);
|
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIMEOUT_US);
|
||||||
if (!decoderFormatRead && outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
if (!decoderFormatRead && outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||||
listener.onVideoMediaFormatRead(mediaCodec.getOutputFormat());
|
listener.onFrameDecoded(mediaCodec.getOutputFormat());
|
||||||
decoderFormatRead = true;
|
decoderFormatRead = true;
|
||||||
}
|
}
|
||||||
assertThat(outputBufferIndex).isNotEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
assertThat(outputBufferIndex).isNotEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
@ -121,12 +167,11 @@ public class FrameProcessorTestUtil {
|
|||||||
|| outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
|| outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||||
mediaCodec.releaseOutputBuffer(outputBufferIndex, /* render= */ true);
|
mediaCodec.releaseOutputBuffer(outputBufferIndex, /* render= */ true);
|
||||||
} finally {
|
} finally {
|
||||||
mediaExtractor.release();
|
|
||||||
if (mediaCodec != null) {
|
if (mediaCodec != null) {
|
||||||
mediaCodec.release();
|
mediaCodec.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private FrameProcessorTestUtil() {}
|
private DecodeOneFrameTestUtil() {}
|
||||||
}
|
}
|
@ -16,20 +16,26 @@
|
|||||||
package androidx.media3.transformer.mh;
|
package androidx.media3.transformer.mh;
|
||||||
|
|
||||||
import static androidx.media3.common.MimeTypes.VIDEO_H265;
|
import static androidx.media3.common.MimeTypes.VIDEO_H265;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER;
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10;
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped;
|
import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped;
|
||||||
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.media.MediaFormat;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.ColorInfo;
|
import androidx.media3.common.ColorInfo;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.util.MediaFormatUtil;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.test.utils.DecodeOneFrameTestUtil;
|
||||||
import androidx.media3.transformer.EncoderUtil;
|
import androidx.media3.transformer.EncoderUtil;
|
||||||
import androidx.media3.transformer.TransformationException;
|
import androidx.media3.transformer.TransformationException;
|
||||||
import androidx.media3.transformer.TransformationRequest;
|
import androidx.media3.transformer.TransformationRequest;
|
||||||
|
import androidx.media3.transformer.TransformationTestResult;
|
||||||
import androidx.media3.transformer.Transformer;
|
import androidx.media3.transformer.Transformer;
|
||||||
import androidx.media3.transformer.TransformerAndroidTestRunner;
|
import androidx.media3.transformer.TransformerAndroidTestRunner;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
@ -62,9 +68,11 @@ public class SetHdrEditingTransformationTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new TransformerAndroidTestRunner.Builder(context, transformer)
|
TransformationTestResult transformationTestResult =
|
||||||
.build()
|
new TransformerAndroidTestRunner.Builder(context, transformer)
|
||||||
.run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10)));
|
.build()
|
||||||
|
.run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10)));
|
||||||
|
checkHasColorTransfer(transformationTestResult, C.COLOR_TRANSFER_ST2084);
|
||||||
return;
|
return;
|
||||||
} catch (TransformationException exception) {
|
} catch (TransformationException exception) {
|
||||||
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
||||||
@ -74,7 +82,6 @@ public class SetHdrEditingTransformationTest {
|
|||||||
.hasCauseThat()
|
.hasCauseThat()
|
||||||
.hasMessageThat()
|
.hasMessageThat()
|
||||||
.isEqualTo("HDR editing and tone mapping not supported under API 31.");
|
.isEqualTo("HDR editing and tone mapping not supported under API 31.");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,9 +106,11 @@ public class SetHdrEditingTransformationTest {
|
|||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
new TransformerAndroidTestRunner.Builder(context, transformer)
|
TransformationTestResult transformationTestResult =
|
||||||
.build()
|
new TransformerAndroidTestRunner.Builder(context, transformer)
|
||||||
.run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10)));
|
.build()
|
||||||
|
.run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10)));
|
||||||
|
checkHasColorTransfer(transformationTestResult, C.COLOR_TRANSFER_ST2084);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -141,9 +150,11 @@ public class SetHdrEditingTransformationTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new TransformerAndroidTestRunner.Builder(context, transformer)
|
TransformationTestResult transformationTestResult =
|
||||||
.build()
|
new TransformerAndroidTestRunner.Builder(context, transformer)
|
||||||
.run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10)));
|
.build()
|
||||||
|
.run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10)));
|
||||||
|
checkHasColorTransfer(transformationTestResult, C.COLOR_TRANSFER_SDR);
|
||||||
} catch (TransformationException exception) {
|
} catch (TransformationException exception) {
|
||||||
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
||||||
// TODO(b/245364266): After fixing the bug, replace the API version check with a check that
|
// TODO(b/245364266): After fixing the bug, replace the API version check with a check that
|
||||||
@ -197,4 +208,30 @@ public class SetHdrEditingTransformationTest {
|
|||||||
private static boolean deviceSupportsHdrEditing(String mimeType, ColorInfo colorInfo) {
|
private static boolean deviceSupportsHdrEditing(String mimeType, ColorInfo colorInfo) {
|
||||||
return !EncoderUtil.getSupportedEncoderNamesForHdrEditing(mimeType, colorInfo).isEmpty();
|
return !EncoderUtil.getSupportedEncoderNamesForHdrEditing(mimeType, colorInfo).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void checkHasColorTransfer(
|
||||||
|
TransformationTestResult transformationTestResult, @C.ColorTransfer int expectedColorTransfer)
|
||||||
|
throws Exception {
|
||||||
|
if (Util.SDK_INT < 29) {
|
||||||
|
// Skipping on this API version due to lack of support for MediaFormat#getInteger, which is
|
||||||
|
// required for MediaFormatUtil#getColorInfo.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DecodeOneFrameTestUtil.decodeOneCacheFileFrame(
|
||||||
|
checkNotNull(transformationTestResult.filePath),
|
||||||
|
new DecodeOneFrameTestUtil.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onContainerExtracted(MediaFormat mediaFormat) {
|
||||||
|
@Nullable ColorInfo extractedColor = MediaFormatUtil.getColorInfo(mediaFormat);
|
||||||
|
assertThat(checkNotNull(extractedColor).colorTransfer).isEqualTo(expectedColorTransfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrameDecoded(MediaFormat mediaFormat) {
|
||||||
|
@Nullable ColorInfo decodedColor = MediaFormatUtil.getColorInfo(mediaFormat);
|
||||||
|
assertThat(checkNotNull(decodedColor).colorTransfer).isEqualTo(expectedColorTransfer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* surface= */ null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user