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()