diff --git a/libraries/effect/src/main/java/androidx/media3/effect/DefaultShaderProgram.java b/libraries/effect/src/main/java/androidx/media3/effect/DefaultShaderProgram.java index 905da71400..2156f5ae10 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/DefaultShaderProgram.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/DefaultShaderProgram.java @@ -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; diff --git a/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoFrameProcessor.java b/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoFrameProcessor.java index 56c90579ca..8fadcdb1f6 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoFrameProcessor.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoFrameProcessor.java @@ -113,7 +113,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { * *

The default value is {@code true}. * - *

If the input or output is HDR, this must be {@code true}. + *

If the output is HDR, this is ignored as the working color space must have a linear + * transfer function. * *

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 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(); diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java index 8770e9838a..ad4c41ceab 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java @@ -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 { diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/ToneMapHdrToSdrUsingOpenGlPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/ToneMapHdrToSdrUsingOpenGlPixelTest.java index 5308fe462f..796735e6fd 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/ToneMapHdrToSdrUsingOpenGlPixelTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/ToneMapHdrToSdrUsingOpenGlPixelTest.java @@ -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()