Effect: support disabling color transfers when HDR->SDR tonemapping
also makes the setter more flexible by ignoring the value of the setter when the output is hdr rather than throwing (since all HDR content must be have a linear color space) PiperOrigin-RevId: 627388436
This commit is contained in:
parent
f2276f613d
commit
73f614b14d
@ -335,10 +335,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
String fragmentShaderFilePath =
|
||||
outputIsHdr
|
||||
? FRAGMENT_SHADER_OETF_ES3_PATH
|
||||
: FRAGMENT_SHADER_TRANSFORMATION_SDR_OETF_ES2_PATH;
|
||||
if (!enableColorTransfers) {
|
||||
fragmentShaderFilePath = FRAGMENT_SHADER_TRANSFORMATION_PATH;
|
||||
}
|
||||
: enableColorTransfers
|
||||
? FRAGMENT_SHADER_TRANSFORMATION_SDR_OETF_ES2_PATH
|
||||
: FRAGMENT_SHADER_TRANSFORMATION_PATH;
|
||||
GlProgram glProgram = createGlProgram(context, vertexShaderFilePath, fragmentShaderFilePath);
|
||||
|
||||
@C.ColorTransfer int outputColorTransfer = outputColorInfo.colorTransfer;
|
||||
|
@ -113,7 +113,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
||||
*
|
||||
* <p>The default value is {@code true}.
|
||||
*
|
||||
* <p>If the input or output is HDR, this must be {@code true}.
|
||||
* <p>If the output is HDR, this is ignored as the working color space must have a linear
|
||||
* transfer function.
|
||||
*
|
||||
* <p>If all input and output content will be SDR, it's recommended to set this value to
|
||||
* {@code false}. This is because 8-bit colors in SDR may result in color banding.
|
||||
@ -348,7 +349,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
||||
|
||||
private final List<Effect> activeEffects;
|
||||
private final Object lock;
|
||||
private final boolean enableColorTransfers;
|
||||
private final ColorInfo outputColorInfo;
|
||||
|
||||
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
|
||||
@ -365,7 +365,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
||||
Executor listenerExecutor,
|
||||
FinalShaderProgramWrapper finalShaderProgramWrapper,
|
||||
boolean renderFramesAutomatically,
|
||||
boolean enableColorTransfers,
|
||||
ColorInfo outputColorInfo) {
|
||||
this.context = context;
|
||||
this.glObjectsProvider = glObjectsProvider;
|
||||
@ -378,7 +377,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
||||
this.renderFramesAutomatically = renderFramesAutomatically;
|
||||
this.activeEffects = new ArrayList<>();
|
||||
this.lock = new Object();
|
||||
this.enableColorTransfers = enableColorTransfers;
|
||||
this.outputColorInfo = outputColorInfo;
|
||||
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
|
||||
this.intermediateGlShaderPrograms = new ArrayList<>();
|
||||
@ -692,10 +690,14 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
||||
.setColorTransfer(C.COLOR_TRANSFER_LINEAR)
|
||||
.setHdrStaticInfo(null)
|
||||
.build();
|
||||
ColorInfo intermediateColorInfo =
|
||||
ColorInfo.isTransferHdr(outputColorInfo)
|
||||
? linearColorInfo
|
||||
: enableColorTransfers ? linearColorInfo : outputColorInfo;
|
||||
InputSwitcher inputSwitcher =
|
||||
new InputSwitcher(
|
||||
context,
|
||||
/* outputColorInfo= */ linearColorInfo,
|
||||
/* outputColorInfo= */ intermediateColorInfo,
|
||||
glObjectsProvider,
|
||||
videoFrameProcessingTaskExecutor,
|
||||
/* errorListenerExecutor= */ videoFrameProcessorListenerExecutor,
|
||||
@ -729,7 +731,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
||||
videoFrameProcessorListenerExecutor,
|
||||
finalShaderProgramWrapper,
|
||||
renderFramesAutomatically,
|
||||
enableColorTransfers,
|
||||
outputColorInfo);
|
||||
}
|
||||
|
||||
@ -846,10 +847,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
||||
*/
|
||||
private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure)
|
||||
throws VideoFrameProcessingException {
|
||||
checkColors(
|
||||
/* inputColorInfo= */ inputStreamInfo.frameInfo.colorInfo,
|
||||
outputColorInfo,
|
||||
enableColorTransfers);
|
||||
checkColors(/* inputColorInfo= */ inputStreamInfo.frameInfo.colorInfo, outputColorInfo);
|
||||
if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) {
|
||||
if (!intermediateGlShaderPrograms.isEmpty()) {
|
||||
for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) {
|
||||
@ -886,14 +884,12 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
||||
}
|
||||
|
||||
/** Checks that color configuration is valid for {@link DefaultVideoFrameProcessor}. */
|
||||
private static void checkColors(
|
||||
ColorInfo inputColorInfo, ColorInfo outputColorInfo, boolean enableColorTransfers)
|
||||
private static void checkColors(ColorInfo inputColorInfo, ColorInfo outputColorInfo)
|
||||
throws VideoFrameProcessingException {
|
||||
if (ColorInfo.isTransferHdr(inputColorInfo)) {
|
||||
checkArgument(inputColorInfo.colorSpace == C.COLOR_SPACE_BT2020);
|
||||
}
|
||||
if ((ColorInfo.isTransferHdr(inputColorInfo) || ColorInfo.isTransferHdr(outputColorInfo))) {
|
||||
checkArgument(enableColorTransfers);
|
||||
long glVersion;
|
||||
try {
|
||||
glVersion = GlUtil.getContextMajorVersion();
|
||||
|
@ -37,6 +37,7 @@ import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.effect.DefaultVideoFrameProcessor;
|
||||
import androidx.media3.transformer.Composition;
|
||||
import androidx.media3.transformer.EditedMediaItem;
|
||||
import androidx.media3.transformer.ExportException;
|
||||
@ -208,6 +209,39 @@ public final class HdrEditingTest {
|
||||
assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportAndTranscodeHdr_withDisabledColorTransfers_whenHdrEditingIsSupported()
|
||||
throws Exception {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
Format format = MP4_ASSET_1080P_5_SECOND_HLG10_FORMAT;
|
||||
assumeDeviceSupportsHdrEditing(testId, format);
|
||||
|
||||
assumeFormatsSupported(context, testId, /* inputFormat= */ format, /* outputFormat= */ format);
|
||||
|
||||
Transformer transformer =
|
||||
new Transformer.Builder(context)
|
||||
.setVideoFrameProcessorFactory(
|
||||
new DefaultVideoFrameProcessor.Factory.Builder()
|
||||
.setEnableColorTransfers(false)
|
||||
.build())
|
||||
.build();
|
||||
EditedMediaItem editedMediaItem =
|
||||
new EditedMediaItem.Builder(MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10)))
|
||||
.setEffects(FORCE_TRANSCODE_VIDEO_EFFECTS)
|
||||
.build();
|
||||
|
||||
ExportTestResult exportTestResult =
|
||||
new TransformerAndroidTestRunner.Builder(context, transformer)
|
||||
.build()
|
||||
.run(testId, editedMediaItem);
|
||||
@C.ColorTransfer
|
||||
int actualColorTransfer =
|
||||
retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO)
|
||||
.colorInfo
|
||||
.colorTransfer;
|
||||
assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportAndTranscode_hdr10File_whenHdrEditingUnsupported_toneMapsOrThrows()
|
||||
throws Exception {
|
||||
|
@ -279,6 +279,46 @@ public final class ToneMapHdrToSdrUsingOpenGlPixelTest {
|
||||
.isAtMost(MAXIMUM_DEVICE_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toneMap_withDisabledColorTransfers_matchesGoldenFile() throws Exception {
|
||||
assumeDeviceSupportsOpenGlToneMapping(testId, HLG_ASSET_FORMAT);
|
||||
videoFrameProcessorTestRunner =
|
||||
new VideoFrameProcessorTestRunner.Builder()
|
||||
.setTestId(testId)
|
||||
.setVideoFrameProcessorFactory(
|
||||
new DefaultVideoFrameProcessor.Factory.Builder()
|
||||
.setEnableColorTransfers(false)
|
||||
.build())
|
||||
.setVideoAssetPath(HLG_ASSET_STRING)
|
||||
.setOutputColorInfo(TONE_MAP_SDR_COLOR)
|
||||
.build();
|
||||
Bitmap expectedBitmap = readBitmap(TONE_MAP_HLG_TO_SDR_PNG_ASSET_PATH);
|
||||
|
||||
Bitmap actualBitmap;
|
||||
try {
|
||||
videoFrameProcessorTestRunner.processFirstFrameAndEnd();
|
||||
actualBitmap = videoFrameProcessorTestRunner.getOutputBitmap();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
if (e.getMessage() != null
|
||||
&& e.getMessage().equals(DecodeOneFrameUtil.NO_DECODER_SUPPORT_ERROR_STRING)) {
|
||||
recordTestSkipped(
|
||||
getApplicationContext(),
|
||||
testId,
|
||||
/* reason= */ DecodeOneFrameUtil.NO_DECODER_SUPPORT_ERROR_STRING);
|
||||
return;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Successfully tone mapped.");
|
||||
// TODO(b/207848601): Switch to using proper tooling for testing against golden data.
|
||||
float averagePixelAbsoluteDifference =
|
||||
getBitmapAveragePixelAbsoluteDifferenceArgb8888(expectedBitmap, actualBitmap, testId);
|
||||
assertThat(averagePixelAbsoluteDifference)
|
||||
.isAtMost(MAXIMUM_DEVICE_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
|
||||
}
|
||||
|
||||
private static VideoFrameProcessorTestRunner.Builder getDefaultFrameProcessorTestRunnerBuilder(
|
||||
String testId) {
|
||||
return new VideoFrameProcessorTestRunner.Builder()
|
||||
|
Loading…
x
Reference in New Issue
Block a user