Commit 7d147509 by huangdarwin Committed by microkatz

HDR: Throw when unexpected color transfer encountered.

This may happen when a containers' color transfer incorrectly does not match
the video's color transfer.

An example of a file with such a mismatch is the current Transformer demo HDR10
sample file.

Manually tested by confirming that no errors are emitted for SDR and HLG sample
files, and that errors are emitted for our incorrect HDR10 sample file.

PiperOrigin-RevId: 461583532
(cherry picked from commit fd046bd2)
parent 26b062bf
...@@ -84,6 +84,9 @@ public final class AndroidTestUtil { ...@@ -84,6 +84,9 @@ public final class AndroidTestUtil {
.setFrameRate(30.472f) .setFrameRate(30.472f)
.build(); .build();
public static final String MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER =
"asset:///media/mp4/hdr10-video-with-sdr-container.mp4";
public static final String MP4_REMOTE_10_SECONDS_URI_STRING = public static final String MP4_REMOTE_10_SECONDS_URI_STRING =
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4"; "https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4";
public static final Format MP4_REMOTE_10_SECONDS_FORMAT = public static final Format MP4_REMOTE_10_SECONDS_FORMAT =
......
/*
* Copyright 2022 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
*
* http://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 com.google.android.exoplayer2.transformer.mh;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.content.Context;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.transformer.TransformationException;
import com.google.android.exoplayer2.transformer.TransformationRequest;
import com.google.android.exoplayer2.transformer.Transformer;
import com.google.android.exoplayer2.transformer.TransformerAndroidTestRunner;
import com.google.android.exoplayer2.util.Util;
import org.junit.Test;
import org.junit.runner.RunWith;
/** {@link Transformer} instrumentation test for applying an HDR frame edit. */
@RunWith(AndroidJUnit4.class)
public class SetHdrEditingTransformationTest {
@Test
public void videoDecoderUnexpectedColorInfo_completesWithError() {
Context context = ApplicationProvider.getApplicationContext();
if (Util.SDK_INT < 24) {
return;
}
Transformer transformer =
new Transformer.Builder(context)
.setTransformationRequest(
new TransformationRequest.Builder().experimental_setEnableHdrEditing(true).build())
.build();
TransformationException exception =
assertThrows(
TransformationException.class,
() ->
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(
/* testId= */ "videoDecoderUnexpectedColorInfo_completesWithError",
MediaItem.fromUri(
Uri.parse(MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER))));
assertThat(exception).hasCauseThat().isInstanceOf(IllegalStateException.class);
assertThat(exception.errorCode).isEqualTo(TransformationException.ERROR_CODE_DECODING_FAILED);
}
}
...@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.Format; ...@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.video.ColorInfo;
import com.google.common.base.Ascii; import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.IOException; import java.io.IOException;
...@@ -51,6 +52,9 @@ public final class DefaultCodec implements Codec { ...@@ -51,6 +52,9 @@ public final class DefaultCodec implements Codec {
private final MediaFormat configurationMediaFormat; private final MediaFormat configurationMediaFormat;
private final Format configurationFormat; private final Format configurationFormat;
/** The expected {@link ColorInfo} output from the codec. */
@Nullable private final ColorInfo configuredOutputColor;
private final MediaCodec mediaCodec; private final MediaCodec mediaCodec;
@Nullable private final Surface inputSurface; @Nullable private final Surface inputSurface;
...@@ -113,6 +117,12 @@ public final class DefaultCodec implements Codec { ...@@ -113,6 +117,12 @@ public final class DefaultCodec implements Codec {
e, configurationMediaFormat, isVideo, isDecoder, mediaCodecName); e, configurationMediaFormat, isVideo, isDecoder, mediaCodecName);
} }
this.mediaCodec = mediaCodec; this.mediaCodec = mediaCodec;
boolean toneMapRequested =
SDK_INT >= 31
&& isDecoder
&& (configurationMediaFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER_REQUEST, 0)
== MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
configuredOutputColor = toneMapRequested ? null : configurationFormat.colorInfo;
this.inputSurface = inputSurface; this.inputSurface = inputSurface;
decoderNeedsFrameDroppingWorkaround = decoderNeedsFrameDroppingWorkaround(context); decoderNeedsFrameDroppingWorkaround = decoderNeedsFrameDroppingWorkaround(context);
} }
...@@ -306,6 +316,18 @@ public final class DefaultCodec implements Codec { ...@@ -306,6 +316,18 @@ public final class DefaultCodec implements Codec {
if (outputBufferIndex < 0) { if (outputBufferIndex < 0) {
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
outputFormat = getFormat(mediaCodec.getOutputFormat()); outputFormat = getFormat(mediaCodec.getOutputFormat());
if (!isColorTransferEqual(configuredOutputColor, outputFormat.colorInfo)) {
// TODO(b/237674316): These exceptions throw when the container ColorInfo doesn't match
// the video ColorInfo. Instead of throwing when seeing unexpected ColorInfos, consider
// reconfiguring downstream components (ex. FrameProcessor and encoder) when different
// ColorInfo values are output.
throw createTransformationException(
new IllegalStateException(
"Codec output color format does not match configured color format. Configured: "
+ configurationFormat.colorInfo
+ ". Actual: "
+ outputFormat.colorInfo));
}
} }
return false; return false;
} }
...@@ -342,12 +364,21 @@ public final class DefaultCodec implements Codec { ...@@ -342,12 +364,21 @@ public final class DefaultCodec implements Codec {
isVideo, isVideo,
isDecoder, isDecoder,
configurationMediaFormat, configurationMediaFormat,
mediaCodec.getName(), getName(),
isDecoder isDecoder
? TransformationException.ERROR_CODE_DECODING_FAILED ? TransformationException.ERROR_CODE_DECODING_FAILED
: TransformationException.ERROR_CODE_ENCODING_FAILED); : TransformationException.ERROR_CODE_ENCODING_FAILED);
} }
private static boolean isColorTransferEqual(
@Nullable ColorInfo colorInfo1, @Nullable ColorInfo colorInfo2) {
@C.ColorTransfer
int transfer1 = (colorInfo1 != null) ? colorInfo1.colorTransfer : C.COLOR_TRANSFER_SDR;
@C.ColorTransfer
int transfer2 = (colorInfo2 != null) ? colorInfo2.colorTransfer : C.COLOR_TRANSFER_SDR;
return transfer1 == transfer2;
}
private static TransformationException createInitializationTransformationException( private static TransformationException createInitializationTransformationException(
Exception cause, Exception cause,
MediaFormat mediaFormat, MediaFormat mediaFormat,
...@@ -394,13 +425,20 @@ public final class DefaultCodec implements Codec { ...@@ -394,13 +425,20 @@ public final class DefaultCodec implements Codec {
} }
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME); String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
Format.Builder formatBuilder = Format.Builder formatBuilder =
new Format.Builder() new Format.Builder().setSampleMimeType(mimeType).setInitializationData(csdBuffers.build());
.setSampleMimeType(mediaFormat.getString(MediaFormat.KEY_MIME))
.setInitializationData(csdBuffers.build());
if (MimeTypes.isVideo(mimeType)) { if (MimeTypes.isVideo(mimeType)) {
formatBuilder formatBuilder
.setWidth(mediaFormat.getInteger(MediaFormat.KEY_WIDTH)) .setWidth(mediaFormat.getInteger(MediaFormat.KEY_WIDTH))
.setHeight(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT)); .setHeight(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT));
if (SDK_INT >= 24) {
// TODO(b/227624622): Set hdrStaticInfo accordingly using KEY_HDR_STATIC_INFO.
formatBuilder.setColorInfo(
new ColorInfo(
mediaFormat.getInteger(MediaFormat.KEY_COLOR_STANDARD),
mediaFormat.getInteger(MediaFormat.KEY_COLOR_RANGE),
mediaFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER),
/* hdrStaticInfo= */ null));
}
} else if (MimeTypes.isAudio(mimeType)) { } else if (MimeTypes.isAudio(mimeType)) {
// TODO(b/178685617): Only set the PCM encoding for audio/raw, once we have a way to // TODO(b/178685617): Only set the PCM encoding for audio/raw, once we have a way to
// simulate more realistic codec input/output formats in tests. // simulate more realistic codec input/output formats in tests.
......
...@@ -150,8 +150,6 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -150,8 +150,6 @@ import org.checkerframework.dataflow.qual.Pure;
decoder = decoder =
decoderFactory.createForVideoDecoding( decoderFactory.createForVideoDecoding(
inputFormat, frameProcessor.getInputSurface(), isToneMappingRequired); inputFormat, frameProcessor.getInputSurface(), isToneMappingRequired);
// TODO(b/236316454): Check in the decoder output format whether tone-mapping was actually
// applied and throw an exception if not.
maxPendingFrameCount = decoder.getMaxPendingFrameCount(); maxPendingFrameCount = decoder.getMaxPendingFrameCount();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment