Effect:migrate setEnableColorTransfers to setSdrWorkingColorSpace

Part of a two stage change to remove the conversion to linear colors in the SDR effects pipeline by default. Changes the boolean to an intdef, introducing a third option that gets all sdr input into the same colorspace.

This is a planned API breaking change, but this change should not change the behavior of the pipeline.

PiperOrigin-RevId: 629013747
This commit is contained in:
tofunmi 2024-04-29 03:24:19 -07:00 committed by Copybara-Service
parent 43de3b67cf
commit 7089253bef
9 changed files with 222 additions and 117 deletions

View File

@ -17,6 +17,7 @@ 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.DefaultVideoFrameProcessor.WORKING_COLOR_SPACE_ORIGINAL;
import static androidx.media3.test.utils.BitmapPixelTestUtil.MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE; import static androidx.media3.test.utils.BitmapPixelTestUtil.MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE;
import static androidx.media3.test.utils.BitmapPixelTestUtil.getBitmapAveragePixelAbsoluteDifferenceArgb8888; import static androidx.media3.test.utils.BitmapPixelTestUtil.getBitmapAveragePixelAbsoluteDifferenceArgb8888;
import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap; import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap;
@ -145,7 +146,7 @@ public final class DefaultVideoFrameProcessorPixelTest {
getDefaultFrameProcessorTestRunnerBuilder(testId) getDefaultFrameProcessorTestRunnerBuilder(testId)
.setVideoFrameProcessorFactory( .setVideoFrameProcessorFactory(
new DefaultVideoFrameProcessor.Factory.Builder() new DefaultVideoFrameProcessor.Factory.Builder()
.setEnableColorTransfers(false) .setSdrWorkingColorSpace(WORKING_COLOR_SPACE_ORIGINAL)
.build()) .build())
.build(); .build();
Bitmap expectedBitmap = readBitmap(ORIGINAL_PNG_ASSET_PATH); Bitmap expectedBitmap = readBitmap(ORIGINAL_PNG_ASSET_PATH);
@ -209,7 +210,7 @@ public final class DefaultVideoFrameProcessorPixelTest {
getDefaultFrameProcessorTestRunnerBuilder(testId) getDefaultFrameProcessorTestRunnerBuilder(testId)
.setVideoFrameProcessorFactory( .setVideoFrameProcessorFactory(
new DefaultVideoFrameProcessor.Factory.Builder() new DefaultVideoFrameProcessor.Factory.Builder()
.setEnableColorTransfers(false) .setSdrWorkingColorSpace(WORKING_COLOR_SPACE_ORIGINAL)
.build()) .build())
.setEffects(NO_OP_EFFECT) .setEffects(NO_OP_EFFECT)
.build(); .build();

View File

@ -16,8 +16,9 @@
// ES 2 fragment shader that: // ES 2 fragment shader that:
// 1. Samples from an external texture with uTexSampler copying from this // 1. Samples from an external texture with uTexSampler copying from this
// texture to the current output. // texture to the current output.
// 2. Transforms the electrical colors to optical colors using the SMPTE 170M // 2. Transforms the electrical colors to "working" colors which is the input
// EOTF. // colorspace with the colors transferred to either linear or SMPTE 170M as
// requested by uSdrWorkingColorSpace.
// 3. Applies a 4x4 RGB color matrix to change the pixel colors. // 3. Applies a 4x4 RGB color matrix to change the pixel colors.
// 4. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR // 4. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
// for outputting to intermediate shaders, or COLOR_TRANSFER_SDR_VIDEO to // for outputting to intermediate shaders, or COLOR_TRANSFER_SDR_VIDEO to
@ -31,12 +32,16 @@ varying vec2 vTexSamplingCoord;
// C.java#ColorTransfer value. // C.java#ColorTransfer value.
// Only COLOR_TRANSFER_LINEAR and COLOR_TRANSFER_SDR_VIDEO are allowed. // Only COLOR_TRANSFER_LINEAR and COLOR_TRANSFER_SDR_VIDEO are allowed.
uniform int uOutputColorTransfer; uniform int uOutputColorTransfer;
uniform int uEnableColorTransfer; uniform int uSdrWorkingColorSpace;
const float inverseGamma = 0.4500; const float inverseGamma = 0.4500;
const float gamma = 1.0 / inverseGamma; const float gamma = 1.0 / inverseGamma;
const int GL_FALSE = 0; const int GL_FALSE = 0;
const int GL_TRUE = 1; const int GL_TRUE = 1;
// LINT.IfChange(working_color_space)
const int WORKING_COLOR_SPACE_DEFAULT = 0;
const int WORKING_COLOR_SPACE_ORIGINAL = 1;
const int WORKING_COLOR_SPACE_LINEAR = 2;
// Transforms a single channel from electrical to optical SDR using the SMPTE // Transforms a single channel from electrical to optical SDR using the SMPTE
// 170M OETF. // 170M OETF.
@ -71,39 +76,57 @@ vec3 smpte170mOetf(vec3 opticalColor) {
smpte170mOetfSingleChannel(opticalColor.b)); smpte170mOetfSingleChannel(opticalColor.b));
} }
// Applies the appropriate OETF to convert linear optical signals to nonlinear // Optionally applies the appropriate EOTF to convert nonlinear electrical
// electrical signals. Input and output are both normalized to [0, 1]. // signals to linear optical signals. Input and output are both normalized to
highp vec3 applyOetf(highp vec3 linearColor) { // [0, 1].
// LINT.IfChange(color_transfer) vec3 convertToWorkingColors(vec3 inputColor) {
const int COLOR_TRANSFER_LINEAR = 1; if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_DEFAULT ||
const int COLOR_TRANSFER_SDR_VIDEO = 3; uSdrWorkingColorSpace == WORKING_COLOR_SPACE_ORIGINAL) {
if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR || return inputColor;
uEnableColorTransfer == GL_FALSE) { } else if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR) {
return linearColor; return smpte170mEotf(inputColor);
} else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
return smpte170mOetf(linearColor);
} else {
// Output red as an obviously visible error.
return vec3(1.0, 0.0, 0.0);
}
}
vec3 applyEotf(vec3 electricalColor) {
if (uEnableColorTransfer == GL_TRUE) {
return smpte170mEotf(electricalColor);
} else if (uEnableColorTransfer == GL_FALSE) {
return electricalColor;
} else { } else {
// Output blue as an obviously visible error. // Output blue as an obviously visible error.
return vec3(0.0, 0.0, 1.0); return vec3(0.0, 0.0, 1.0);
} }
} }
// Optionally applies the appropriate OETF to convert linear optical signals to
// nonlinear electrical signals. Input and output are both normalized to [0, 1].
highp vec3 convertToOutputColors(highp vec3 workingColors) {
// LINT.IfChange(color_transfer)
const int COLOR_TRANSFER_LINEAR = 1;
const int COLOR_TRANSFER_SDR_VIDEO = 3;
if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_DEFAULT) {
if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR) {
return smpte170mEotf(workingColors);
} else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
return workingColors;
} else {
// Output red as an obviously visible error.
return vec3(1.0, 0.0, 0.0);
}
} else if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_ORIGINAL) {
return workingColors;
} else if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR) {
if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR) {
return workingColors;
} else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
return smpte170mOetf(workingColors);
} else {
// Output red as an obviously visible error.
return vec3(1.0, 0.0, 0.0);
}
} else {
// Output red as an obviously visible error.
return vec3(1.0, 0.0, 0.0);
}
}
void main() { void main() {
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord); vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
vec3 linearInputColor = applyEotf(inputColor.rgb); vec3 workingColors = convertToWorkingColors(inputColor.rgb);
vec4 transformedColors = uRgbMatrix * vec4(workingColors, 1);
vec4 transformedColors = uRgbMatrix * vec4(linearInputColor, 1); gl_FragColor =
vec4(convertToOutputColors(transformedColors.rgb), inputColor.a);
gl_FragColor = vec4(applyOetf(transformedColors.rgb), inputColor.a);
} }

View File

@ -17,8 +17,9 @@
// 1. Samples from an input texture created from an internal texture (e.g. a // 1. Samples from an input texture created from an internal texture (e.g. a
// texture created from a bitmap), with uTexSampler copying from this texture // texture created from a bitmap), with uTexSampler copying from this texture
// to the current output. // to the current output.
// 2. Transforms the electrical colors to optical colors using the SMPTE 170M // 2. Transforms the electrical colors to "working" colors which is the input
// EOTF or the sRGB EOTF, as requested by uInputColorTransfer. // colorspace with the colors transferred to either linear or SMPTE 170M as
// requested by uSdrWorkingColorSpace.
// 3. Applies a 4x4 RGB color matrix to change the pixel colors. // 3. Applies a 4x4 RGB color matrix to change the pixel colors.
// 4. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR // 4. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
// for outputting to intermediate shaders, or COLOR_TRANSFER_SDR_VIDEO to // for outputting to intermediate shaders, or COLOR_TRANSFER_SDR_VIDEO to
@ -34,7 +35,7 @@ uniform int uInputColorTransfer;
// C.java#ColorTransfer value. // C.java#ColorTransfer value.
// Only COLOR_TRANSFER_LINEAR and COLOR_TRANSFER_SDR_VIDEO are allowed. // Only COLOR_TRANSFER_LINEAR and COLOR_TRANSFER_SDR_VIDEO are allowed.
uniform int uOutputColorTransfer; uniform int uOutputColorTransfer;
uniform int uEnableColorTransfer; uniform int uSdrWorkingColorSpace;
const float inverseGamma = 0.4500; const float inverseGamma = 0.4500;
const float gamma = 1.0 / inverseGamma; const float gamma = 1.0 / inverseGamma;
@ -44,6 +45,10 @@ const int GL_TRUE = 1;
const int COLOR_TRANSFER_LINEAR = 1; const int COLOR_TRANSFER_LINEAR = 1;
const int COLOR_TRANSFER_SRGB = 2; const int COLOR_TRANSFER_SRGB = 2;
const int COLOR_TRANSFER_SDR_VIDEO = 3; const int COLOR_TRANSFER_SDR_VIDEO = 3;
// LINT.IfChange(working_color_space)
const int WORKING_COLOR_SPACE_DEFAULT = 0;
const int WORKING_COLOR_SPACE_ORIGINAL = 1;
const int WORKING_COLOR_SPACE_LINEAR = 2;
// Transforms a single channel from electrical to optical SDR using the sRGB // Transforms a single channel from electrical to optical SDR using the sRGB
// EOTF. // EOTF.
@ -56,7 +61,7 @@ float srgbEotfSingleChannel(float electricalChannel) {
} }
// Transforms electrical to optical SDR using the sRGB EOTF. // Transforms electrical to optical SDR using the sRGB EOTF.
vec3 srgbEotf(const vec3 electricalColor) { vec3 srgbEotf(vec3 electricalColor) {
return vec3(srgbEotfSingleChannel(electricalColor.r), return vec3(srgbEotfSingleChannel(electricalColor.r),
srgbEotfSingleChannel(electricalColor.g), srgbEotfSingleChannel(electricalColor.g),
srgbEotfSingleChannel(electricalColor.b)); srgbEotfSingleChannel(electricalColor.b));
@ -94,34 +99,60 @@ vec3 smpte170mOetf(vec3 opticalColor) {
smpte170mOetfSingleChannel(opticalColor.g), smpte170mOetfSingleChannel(opticalColor.g),
smpte170mOetfSingleChannel(opticalColor.b)); smpte170mOetfSingleChannel(opticalColor.b));
} }
// Applies the appropriate EOTF to convert nonlinear electrical signals to
// linear optical signals. Input and output are both normalized to [0, 1]. // Optionally applies the appropriate EOTF to convert nonlinear electrical
vec3 applyEotf(vec3 electricalColor) { // signals to linear optical signals. Input and output are both normalized to
if (uEnableColorTransfer == GL_TRUE) { // [0, 1].
vec3 convertToWorkingColors(vec3 inputColor) {
if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_DEFAULT) {
if (uInputColorTransfer == COLOR_TRANSFER_SRGB) { if (uInputColorTransfer == COLOR_TRANSFER_SRGB) {
return srgbEotf(electricalColor); return smpte170mOetf(srgbEotf(inputColor));
} else if (uInputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) { } else if (uInputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
return smpte170mEotf(electricalColor); return inputColor;
} else {
// Output blue as an obviously visible error.
return vec3(0.0, 0.0, 1.0);
}
} else if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_ORIGINAL) {
return inputColor;
} else if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR) {
if (uInputColorTransfer == COLOR_TRANSFER_SRGB) {
return srgbEotf(inputColor);
} else if (uInputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
return smpte170mEotf(inputColor);
} else { } else {
// Output blue as an obviously visible error. // Output blue as an obviously visible error.
return vec3(0.0, 0.0, 1.0); return vec3(0.0, 0.0, 1.0);
} }
} else if (uEnableColorTransfer == GL_FALSE) {
return electricalColor;
} else { } else {
// Output blue as an obviously visible error. // Output blue as an obviously visible error.
return vec3(0.0, 0.0, 1.0); return vec3(0.0, 0.0, 1.0);
} }
} }
// Applies the appropriate OETF to convert linear optical signals to nonlinear // Optionally applies the appropriate OETF to convert linear optical signals to
// electrical signals. Input and output are both normalized to [0, 1]. // nonlinear electrical signals. Input and output are both normalized to [0, 1].
highp vec3 applyOetf(highp vec3 linearColor) { highp vec3 convertToOutputColors(highp vec3 workingColors) {
if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR || if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_DEFAULT) {
uEnableColorTransfer == GL_FALSE) { if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR) {
return linearColor; return smpte170mEotf(workingColors);
} else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) { } else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
return smpte170mOetf(linearColor); return workingColors;
} else {
// Output red as an obviously visible error.
return vec3(1.0, 0.0, 0.0);
}
} else if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_ORIGINAL) {
return workingColors;
} else if (uSdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR) {
if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR) {
return workingColors;
} else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
return smpte170mOetf(workingColors);
} else {
// Output red as an obviously visible error.
return vec3(1.0, 0.0, 0.0);
}
} else { } else {
// Output red as an obviously visible error. // Output red as an obviously visible error.
return vec3(1.0, 0.0, 0.0); return vec3(1.0, 0.0, 0.0);
@ -143,8 +174,8 @@ vec2 getAdjustedTexSamplingCoord(vec2 originalTexSamplingCoord) {
void main() { void main() {
vec4 inputColor = vec4 inputColor =
texture2D(uTexSampler, getAdjustedTexSamplingCoord(vTexSamplingCoord)); texture2D(uTexSampler, getAdjustedTexSamplingCoord(vTexSamplingCoord));
vec3 linearInputColor = applyEotf(inputColor.rgb); vec3 workingColors = convertToWorkingColors(inputColor.rgb);
vec4 transformedColors = uRgbMatrix * vec4(linearInputColor, 1); vec4 transformedColors = uRgbMatrix * vec4(workingColors, 1);
gl_FragColor =
gl_FragColor = vec4(applyOetf(transformedColors.rgb), inputColor.a); vec4(convertToOutputColors(transformedColors.rgb), inputColor.a);
} }

View File

@ -20,6 +20,7 @@ import static android.opengl.GLES20.GL_TRUE;
import static androidx.media3.common.VideoFrameProcessor.INPUT_TYPE_BITMAP; import static androidx.media3.common.VideoFrameProcessor.INPUT_TYPE_BITMAP;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.effect.DefaultVideoFrameProcessor.WORKING_COLOR_SPACE_LINEAR;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -38,6 +39,7 @@ import androidx.media3.common.util.GlUtil.GlException;
import androidx.media3.common.util.Size; import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.effect.DefaultVideoFrameProcessor.WorkingColorSpace;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -182,6 +184,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
createGlProgram( createGlProgram(
context, VERTEX_SHADER_TRANSFORMATION_PATH, FRAGMENT_SHADER_TRANSFORMATION_PATH); context, VERTEX_SHADER_TRANSFORMATION_PATH, FRAGMENT_SHADER_TRANSFORMATION_PATH);
// TODO: b/263306471 - when default working color space changes to WORKING_COLOR_SPACE_DEFAULT,
// make sure no color transfers are applied in shader.
// No transfer functions needed, because input and output are both optical colors. // No transfer functions needed, because input and output are both optical colors.
return new DefaultShaderProgram( return new DefaultShaderProgram(
glProgram, glProgram,
@ -206,8 +211,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param outputColorInfo The output electrical (nonlinear) or optical (linear) {@link ColorInfo}. * @param outputColorInfo The output electrical (nonlinear) or optical (linear) {@link ColorInfo}.
* If this is an optical color, it must be BT.2020 if {@code inputColorInfo} is {@linkplain * If this is an optical color, it must be BT.2020 if {@code inputColorInfo} is {@linkplain
* ColorInfo#isTransferHdr(ColorInfo) HDR}, and RGB BT.709 if not. * ColorInfo#isTransferHdr(ColorInfo) HDR}, and RGB BT.709 if not.
* @param enableColorTransfers Whether to transfer colors to an intermediate color space when * @param sdrWorkingColorSpace The {@link WorkingColorSpace} to apply effects in.
* applying effects. If the input or output is HDR, this must be {@code true}.
* @throws VideoFrameProcessingException If a problem occurs while reading shader files or an * @throws VideoFrameProcessingException If a problem occurs while reading shader files or an
* OpenGL operation fails or is unsupported. * OpenGL operation fails or is unsupported.
*/ */
@ -215,7 +219,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Context context, Context context,
ColorInfo inputColorInfo, ColorInfo inputColorInfo,
ColorInfo outputColorInfo, ColorInfo outputColorInfo,
boolean enableColorTransfers, @WorkingColorSpace int sdrWorkingColorSpace,
@InputType int inputType) @InputType int inputType)
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
checkState( checkState(
@ -242,7 +246,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
"uApplyHdrToSdrToneMapping", "uApplyHdrToSdrToneMapping",
outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020 ? GL_TRUE : GL_FALSE); outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020 ? GL_TRUE : GL_FALSE);
} }
return createWithSampler(glProgram, inputColorInfo, outputColorInfo, enableColorTransfers); return createWithSampler(glProgram, inputColorInfo, outputColorInfo, sdrWorkingColorSpace);
} }
/** /**
@ -262,8 +266,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param outputColorInfo The output electrical (nonlinear) or optical (linear) {@link ColorInfo}. * @param outputColorInfo The output electrical (nonlinear) or optical (linear) {@link ColorInfo}.
* If this is an optical color, it must be BT.2020 if {@code inputColorInfo} is {@linkplain * If this is an optical color, it must be BT.2020 if {@code inputColorInfo} is {@linkplain
* ColorInfo#isTransferHdr(ColorInfo) HDR}, and RGB BT.709 if not. * ColorInfo#isTransferHdr(ColorInfo) HDR}, and RGB BT.709 if not.
* @param enableColorTransfers Whether to transfer colors to an intermediate color space when * @param sdrWorkingColorSpace The {@link WorkingColorSpace} to apply effects in.
* applying effects. If the input or output is HDR, this must be {@code true}.
* @throws VideoFrameProcessingException If a problem occurs while reading shader files or an * @throws VideoFrameProcessingException If a problem occurs while reading shader files or an
* OpenGL operation fails or is unsupported. * OpenGL operation fails or is unsupported.
*/ */
@ -271,7 +274,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Context context, Context context,
ColorInfo inputColorInfo, ColorInfo inputColorInfo,
ColorInfo outputColorInfo, ColorInfo outputColorInfo,
boolean enableColorTransfers) @WorkingColorSpace int sdrWorkingColorSpace)
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
boolean isInputTransferHdr = ColorInfo.isTransferHdr(inputColorInfo); boolean isInputTransferHdr = ColorInfo.isTransferHdr(inputColorInfo);
String vertexShaderFilePath = String vertexShaderFilePath =
@ -300,7 +303,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020 ? GL_TRUE : GL_FALSE); outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020 ? GL_TRUE : GL_FALSE);
} }
return createWithSampler(glProgram, inputColorInfo, outputColorInfo, enableColorTransfers); return createWithSampler(glProgram, inputColorInfo, outputColorInfo, sdrWorkingColorSpace);
} }
/** /**
@ -319,6 +322,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param rgbMatrices The {@link RgbMatrix RgbMatrices} to apply to each frame in order. Can be * @param rgbMatrices The {@link RgbMatrix RgbMatrices} to apply to each frame in order. Can be
* empty to apply no color transformations. * empty to apply no color transformations.
* @param outputColorInfo The electrical (non-linear) {@link ColorInfo} describing output colors. * @param outputColorInfo The electrical (non-linear) {@link ColorInfo} describing output colors.
* @param sdrWorkingColorSpace The {@link WorkingColorSpace} to apply effects in.
* @throws VideoFrameProcessingException If a problem occurs while reading shader files or an * @throws VideoFrameProcessingException If a problem occurs while reading shader files or an
* OpenGL operation fails or is unsupported. * OpenGL operation fails or is unsupported.
*/ */
@ -327,15 +331,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
List<GlMatrixTransformation> matrixTransformations, List<GlMatrixTransformation> matrixTransformations,
List<RgbMatrix> rgbMatrices, List<RgbMatrix> rgbMatrices,
ColorInfo outputColorInfo, ColorInfo outputColorInfo,
boolean enableColorTransfers) @WorkingColorSpace int sdrWorkingColorSpace)
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
boolean outputIsHdr = ColorInfo.isTransferHdr(outputColorInfo); boolean outputIsHdr = ColorInfo.isTransferHdr(outputColorInfo);
boolean shouldApplyOetf = sdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR;
String vertexShaderFilePath = String vertexShaderFilePath =
outputIsHdr ? VERTEX_SHADER_TRANSFORMATION_ES3_PATH : VERTEX_SHADER_TRANSFORMATION_PATH; outputIsHdr ? VERTEX_SHADER_TRANSFORMATION_ES3_PATH : VERTEX_SHADER_TRANSFORMATION_PATH;
String fragmentShaderFilePath = String fragmentShaderFilePath =
outputIsHdr outputIsHdr
? FRAGMENT_SHADER_OETF_ES3_PATH ? FRAGMENT_SHADER_OETF_ES3_PATH
: enableColorTransfers : shouldApplyOetf
? FRAGMENT_SHADER_TRANSFORMATION_SDR_OETF_ES2_PATH ? FRAGMENT_SHADER_TRANSFORMATION_SDR_OETF_ES2_PATH
: FRAGMENT_SHADER_TRANSFORMATION_PATH; : FRAGMENT_SHADER_TRANSFORMATION_PATH;
GlProgram glProgram = createGlProgram(context, vertexShaderFilePath, fragmentShaderFilePath); GlProgram glProgram = createGlProgram(context, vertexShaderFilePath, fragmentShaderFilePath);
@ -346,7 +351,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputColorTransfer == C.COLOR_TRANSFER_HLG outputColorTransfer == C.COLOR_TRANSFER_HLG
|| outputColorTransfer == C.COLOR_TRANSFER_ST2084); || outputColorTransfer == C.COLOR_TRANSFER_ST2084);
glProgram.setIntUniform("uOutputColorTransfer", outputColorTransfer); glProgram.setIntUniform("uOutputColorTransfer", outputColorTransfer);
} else if (enableColorTransfers) { } else if (shouldApplyOetf) {
checkArgument( checkArgument(
outputColorTransfer == C.COLOR_TRANSFER_SDR outputColorTransfer == C.COLOR_TRANSFER_SDR
|| outputColorTransfer == C.COLOR_TRANSFER_GAMMA_2_2); || outputColorTransfer == C.COLOR_TRANSFER_GAMMA_2_2);
@ -365,7 +370,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
GlProgram glProgram, GlProgram glProgram,
ColorInfo inputColorInfo, ColorInfo inputColorInfo,
ColorInfo outputColorInfo, ColorInfo outputColorInfo,
boolean enableColorTransfers) { @WorkingColorSpace int sdrWorkingColorSpace) {
boolean isInputTransferHdr = ColorInfo.isTransferHdr(inputColorInfo); boolean isInputTransferHdr = ColorInfo.isTransferHdr(inputColorInfo);
boolean isExpandingColorGamut = boolean isExpandingColorGamut =
(inputColorInfo.colorSpace == C.COLOR_SPACE_BT709 (inputColorInfo.colorSpace == C.COLOR_SPACE_BT709
@ -384,7 +389,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} else if (isExpandingColorGamut) { } else if (isExpandingColorGamut) {
glProgram.setIntUniform("uOutputColorTransfer", outputColorTransfer); glProgram.setIntUniform("uOutputColorTransfer", outputColorTransfer);
} else { } else {
glProgram.setIntUniform("uEnableColorTransfer", enableColorTransfers ? GL_TRUE : GL_FALSE); glProgram.setIntUniform("uSdrWorkingColorSpace", sdrWorkingColorSpace);
checkArgument( checkArgument(
outputColorTransfer == C.COLOR_TRANSFER_SDR outputColorTransfer == C.COLOR_TRANSFER_SDR
|| outputColorTransfer == C.COLOR_TRANSFER_LINEAR); || outputColorTransfer == C.COLOR_TRANSFER_LINEAR);

View File

@ -25,6 +25,7 @@ import static androidx.media3.effect.DebugTraceUtil.EVENT_VFP_REGISTER_NEW_INPUT
import static androidx.media3.effect.DebugTraceUtil.EVENT_VFP_SIGNAL_ENDED; import static androidx.media3.effect.DebugTraceUtil.EVENT_VFP_SIGNAL_ENDED;
import static androidx.media3.effect.DebugTraceUtil.logEvent; import static androidx.media3.effect.DebugTraceUtil.logEvent;
import static com.google.common.collect.Iterables.getFirst; import static com.google.common.collect.Iterables.getFirst;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -35,6 +36,7 @@ import android.opengl.GLES20;
import android.opengl.GLES30; import android.opengl.GLES30;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
@ -57,6 +59,10 @@ import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -80,13 +86,52 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
void release(long presentationTimeUs); void release(long presentationTimeUs);
} }
// LINT.IfChange(working_color_space)
/**
* Specifies the color space that frames passed to intermediate {@link GlShaderProgram}s will be
* represented in.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({WORKING_COLOR_SPACE_DEFAULT, WORKING_COLOR_SPACE_ORIGINAL, WORKING_COLOR_SPACE_LINEAR})
public @interface WorkingColorSpace {}
/**
* Use BT709 color primaries with the standard SDR transfer function (SMPTE 170m) as the working
* color space.
*
* <p>Any SDR content in a different color space will be transferred to this one.
*/
public static final int WORKING_COLOR_SPACE_DEFAULT = 0;
/**
* Use the original color space of the input as the working color space when the input is SDR.
*
* <p>Tonemapped HDR content will be represented with BT709 color primaries and the standard SDR
* transfer function (SMPTE 170m).
*
* <p>No color transfers will be applied when the input is SDR.
*/
public static final int WORKING_COLOR_SPACE_ORIGINAL = 1;
/**
* The working color space will have the same primaries as the input and a linear transfer
* function.
*
* <p>This option is not recommended for SDR content since it may lead to color banding since
* 8-bit colors are used in SDR processing. It may also cause effects that modify a frame's output
* colors (for example {@linkplain OverlayEffect overlays}) to have incorrect output colors.
*/
public static final int WORKING_COLOR_SPACE_LINEAR = 2;
/** A factory for {@link DefaultVideoFrameProcessor} instances. */ /** A factory for {@link DefaultVideoFrameProcessor} instances. */
public static final class Factory implements VideoFrameProcessor.Factory { public static final class Factory implements VideoFrameProcessor.Factory {
private static final String THREAD_NAME = "Effect:DefaultVideoFrameProcessor:GlThread"; private static final String THREAD_NAME = "Effect:DefaultVideoFrameProcessor:GlThread";
/** A builder for {@link DefaultVideoFrameProcessor.Factory} instances. */ /** A builder for {@link DefaultVideoFrameProcessor.Factory} instances. */
public static final class Builder { public static final class Builder {
private boolean enableColorTransfers; private @WorkingColorSpace int sdrWorkingColorSpace;
@Nullable private ExecutorService executorService; @Nullable private ExecutorService executorService;
private @MonotonicNonNull GlObjectsProvider glObjectsProvider; private @MonotonicNonNull GlObjectsProvider glObjectsProvider;
private GlTextureProducer.@MonotonicNonNull Listener textureOutputListener; private GlTextureProducer.@MonotonicNonNull Listener textureOutputListener;
@ -95,12 +140,12 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
/** Creates an instance. */ /** Creates an instance. */
public Builder() { public Builder() {
enableColorTransfers = true; sdrWorkingColorSpace = WORKING_COLOR_SPACE_LINEAR;
requireRegisteringAllInputFrames = true; requireRegisteringAllInputFrames = true;
} }
private Builder(Factory factory) { private Builder(Factory factory) {
enableColorTransfers = factory.enableColorTransfers; sdrWorkingColorSpace = factory.sdrWorkingColorSpace;
executorService = factory.executorService; executorService = factory.executorService;
glObjectsProvider = factory.glObjectsProvider; glObjectsProvider = factory.glObjectsProvider;
textureOutputListener = factory.textureOutputListener; textureOutputListener = factory.textureOutputListener;
@ -108,22 +153,19 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
requireRegisteringAllInputFrames = !factory.repeatLastRegisteredFrame; requireRegisteringAllInputFrames = !factory.repeatLastRegisteredFrame;
} }
// TODO: b/263306471 - Change default to WORKING_COLOR_SPACE_DEFAULT.
/** /**
* Sets whether to transfer colors to an intermediate color space when applying effects. * Sets the {@link WorkingColorSpace} in which frames passed to intermediate effects will be
* represented.
* *
* <p>The default value is {@code true}. * <p>The default value is {@link #WORKING_COLOR_SPACE_LINEAR}.
* *
* <p>If the output is HDR, this is ignored as the working color space must have a linear * <p>This setter doesn't affect the working color space for HDR output, since the working
* transfer function. * color space must have a linear transfer function for HDR output.
*
* <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.
*
* <p>This doesn't currently work with overlay effects (ex. {@link TextureOverlay}).
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public Builder setEnableColorTransfers(boolean enableColorTransfers) { public Builder setSdrWorkingColorSpace(@WorkingColorSpace int sdrWorkingColorSpace) {
this.enableColorTransfers = enableColorTransfers; this.sdrWorkingColorSpace = sdrWorkingColorSpace;
return this; return this;
} }
@ -215,7 +257,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
/** Builds an {@link DefaultVideoFrameProcessor.Factory} instance. */ /** Builds an {@link DefaultVideoFrameProcessor.Factory} instance. */
public DefaultVideoFrameProcessor.Factory build() { public DefaultVideoFrameProcessor.Factory build() {
return new DefaultVideoFrameProcessor.Factory( return new DefaultVideoFrameProcessor.Factory(
enableColorTransfers, sdrWorkingColorSpace,
/* repeatLastRegisteredFrame= */ !requireRegisteringAllInputFrames, /* repeatLastRegisteredFrame= */ !requireRegisteringAllInputFrames,
glObjectsProvider == null ? new DefaultGlObjectsProvider() : glObjectsProvider, glObjectsProvider == null ? new DefaultGlObjectsProvider() : glObjectsProvider,
executorService, executorService,
@ -224,7 +266,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
} }
} }
private final boolean enableColorTransfers; private final @WorkingColorSpace int sdrWorkingColorSpace;
private final boolean repeatLastRegisteredFrame; private final boolean repeatLastRegisteredFrame;
private final GlObjectsProvider glObjectsProvider; private final GlObjectsProvider glObjectsProvider;
@Nullable private final ExecutorService executorService; @Nullable private final ExecutorService executorService;
@ -232,13 +274,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private final int textureOutputCapacity; private final int textureOutputCapacity;
private Factory( private Factory(
boolean enableColorTransfers, @WorkingColorSpace int sdrWorkingColorSpace,
boolean repeatLastRegisteredFrame, boolean repeatLastRegisteredFrame,
GlObjectsProvider glObjectsProvider, GlObjectsProvider glObjectsProvider,
@Nullable ExecutorService executorService, @Nullable ExecutorService executorService,
@Nullable GlTextureProducer.Listener textureOutputListener, @Nullable GlTextureProducer.Listener textureOutputListener,
int textureOutputCapacity) { int textureOutputCapacity) {
this.enableColorTransfers = enableColorTransfers; this.sdrWorkingColorSpace = sdrWorkingColorSpace;
this.repeatLastRegisteredFrame = repeatLastRegisteredFrame; this.repeatLastRegisteredFrame = repeatLastRegisteredFrame;
this.glObjectsProvider = glObjectsProvider; this.glObjectsProvider = glObjectsProvider;
this.executorService = executorService; this.executorService = executorService;
@ -298,7 +340,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
context, context,
debugViewProvider, debugViewProvider,
outputColorInfo, outputColorInfo,
enableColorTransfers, sdrWorkingColorSpace,
renderFramesAutomatically, renderFramesAutomatically,
videoFrameProcessingTaskExecutor, videoFrameProcessingTaskExecutor,
listenerExecutor, listenerExecutor,
@ -488,10 +530,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
* {@link C#COLOR_TRANSFER_GAMMA_2_2}, for consistency with other tone-mapping and color behavior * {@link C#COLOR_TRANSFER_GAMMA_2_2}, for consistency with other tone-mapping and color behavior
* in the Android ecosystem (for example, MediaFormat's COLOR_TRANSFER_SDR_VIDEO is defined as * in the Android ecosystem (for example, MediaFormat's COLOR_TRANSFER_SDR_VIDEO is defined as
* SMPTE 170M, but most OEMs process it as Gamma 2.2). * SMPTE 170M, but most OEMs process it as Gamma 2.2).
*
* <p>If either {@link FrameInfo#colorInfo} or {@code outputColorInfo} {@linkplain
* ColorInfo#isTransferHdr} are HDR}, color transfers must {@linkplain
* Factory.Builder#setEnableColorTransfers be enabled}.
*/ */
@Override @Override
public void registerInputStream( public void registerInputStream(
@ -666,7 +704,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
Context context, Context context,
DebugViewProvider debugViewProvider, DebugViewProvider debugViewProvider,
ColorInfo outputColorInfo, ColorInfo outputColorInfo,
boolean enableColorTransfers, @WorkingColorSpace int sdrWorkingColorSpace,
boolean renderFramesAutomatically, boolean renderFramesAutomatically,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor, VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Executor videoFrameProcessorListenerExecutor, Executor videoFrameProcessorListenerExecutor,
@ -693,7 +731,9 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
ColorInfo intermediateColorInfo = ColorInfo intermediateColorInfo =
ColorInfo.isTransferHdr(outputColorInfo) ColorInfo.isTransferHdr(outputColorInfo)
? linearColorInfo ? linearColorInfo
: enableColorTransfers ? linearColorInfo : outputColorInfo; : sdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR
? linearColorInfo
: outputColorInfo;
InputSwitcher inputSwitcher = InputSwitcher inputSwitcher =
new InputSwitcher( new InputSwitcher(
context, context,
@ -702,7 +742,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
videoFrameProcessingTaskExecutor, videoFrameProcessingTaskExecutor,
/* errorListenerExecutor= */ videoFrameProcessorListenerExecutor, /* errorListenerExecutor= */ videoFrameProcessorListenerExecutor,
/* samplingShaderProgramErrorListener= */ listener::onError, /* samplingShaderProgramErrorListener= */ listener::onError,
enableColorTransfers, sdrWorkingColorSpace,
repeatLastRegisteredFrame); repeatLastRegisteredFrame);
FinalShaderProgramWrapper finalShaderProgramWrapper = FinalShaderProgramWrapper finalShaderProgramWrapper =
@ -712,13 +752,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
eglContext, eglContext,
debugViewProvider, debugViewProvider,
outputColorInfo, outputColorInfo,
enableColorTransfers,
renderFramesAutomatically,
videoFrameProcessingTaskExecutor, videoFrameProcessingTaskExecutor,
videoFrameProcessorListenerExecutor, videoFrameProcessorListenerExecutor,
listener, listener,
textureOutputListener, textureOutputListener,
textureOutputCapacity); textureOutputCapacity,
sdrWorkingColorSpace,
renderFramesAutomatically);
return new DefaultVideoFrameProcessor( return new DefaultVideoFrameProcessor(
context, context,

View File

@ -17,6 +17,7 @@ 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.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.effect.DefaultVideoFrameProcessor.WORKING_COLOR_SPACE_LINEAR;
import android.content.Context; import android.content.Context;
import android.opengl.EGL14; import android.opengl.EGL14;
@ -44,6 +45,7 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.LongArrayQueue; import androidx.media3.common.util.LongArrayQueue;
import androidx.media3.common.util.Size; import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.effect.DefaultVideoFrameProcessor.WorkingColorSpace;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -81,8 +83,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final EGLContext eglContext; private final EGLContext eglContext;
private final DebugViewProvider debugViewProvider; private final DebugViewProvider debugViewProvider;
private final ColorInfo outputColorInfo; private final ColorInfo outputColorInfo;
private final boolean enableColorTransfers;
private final boolean renderFramesAutomatically;
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor; private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
private final Executor videoFrameProcessorListenerExecutor; private final Executor videoFrameProcessorListenerExecutor;
private final VideoFrameProcessor.Listener videoFrameProcessorListener; private final VideoFrameProcessor.Listener videoFrameProcessorListener;
@ -91,6 +91,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final LongArrayQueue outputTextureTimestamps; // Synchronized with outputTexturePool. private final LongArrayQueue outputTextureTimestamps; // Synchronized with outputTexturePool.
private final LongArrayQueue syncObjects; private final LongArrayQueue syncObjects;
@Nullable private final GlTextureProducer.Listener textureOutputListener; @Nullable private final GlTextureProducer.Listener textureOutputListener;
private final @WorkingColorSpace int sdrWorkingColorSpace;
private final boolean renderFramesAutomatically;
private int inputWidth; private int inputWidth;
private int inputHeight; private int inputHeight;
@ -122,13 +124,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
EGLContext eglContext, EGLContext eglContext,
DebugViewProvider debugViewProvider, DebugViewProvider debugViewProvider,
ColorInfo outputColorInfo, ColorInfo outputColorInfo,
boolean enableColorTransfers,
boolean renderFramesAutomatically,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor, VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Executor videoFrameProcessorListenerExecutor, Executor videoFrameProcessorListenerExecutor,
VideoFrameProcessor.Listener videoFrameProcessorListener, VideoFrameProcessor.Listener videoFrameProcessorListener,
@Nullable GlTextureProducer.Listener textureOutputListener, @Nullable Listener textureOutputListener,
int textureOutputCapacity) { int textureOutputCapacity,
@WorkingColorSpace int sdrWorkingColorSpace,
boolean renderFramesAutomatically) {
this.context = context; this.context = context;
this.matrixTransformations = new ArrayList<>(); this.matrixTransformations = new ArrayList<>();
this.rgbMatrices = new ArrayList<>(); this.rgbMatrices = new ArrayList<>();
@ -136,12 +138,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.eglContext = eglContext; this.eglContext = eglContext;
this.debugViewProvider = debugViewProvider; this.debugViewProvider = debugViewProvider;
this.outputColorInfo = outputColorInfo; this.outputColorInfo = outputColorInfo;
this.enableColorTransfers = enableColorTransfers;
this.renderFramesAutomatically = renderFramesAutomatically;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor; this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
this.videoFrameProcessorListenerExecutor = videoFrameProcessorListenerExecutor; this.videoFrameProcessorListenerExecutor = videoFrameProcessorListenerExecutor;
this.videoFrameProcessorListener = videoFrameProcessorListener; this.videoFrameProcessorListener = videoFrameProcessorListener;
this.textureOutputListener = textureOutputListener; this.textureOutputListener = textureOutputListener;
this.sdrWorkingColorSpace = sdrWorkingColorSpace;
this.renderFramesAutomatically = renderFramesAutomatically;
inputListener = new InputListener() {}; inputListener = new InputListener() {};
availableFrames = new ConcurrentLinkedQueue<>(); availableFrames = new ConcurrentLinkedQueue<>();
@ -525,7 +527,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
expandedMatrixTransformations, expandedMatrixTransformations,
rgbMatrices, rgbMatrices,
outputColorInfo, outputColorInfo,
enableColorTransfers); sdrWorkingColorSpace);
Size outputSize = defaultShaderProgram.configure(inputWidth, inputHeight); Size outputSize = defaultShaderProgram.configure(inputWidth, inputHeight);
if (outputSurfaceInfo != null) { if (outputSurfaceInfo != null) {
@ -545,7 +547,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
.maybeRenderToSurfaceView( .maybeRenderToSurfaceView(
() -> { () -> {
GlUtil.clearFocusedBuffers(); GlUtil.clearFocusedBuffers();
if (enableColorTransfers) { if (sdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR) {
@C.ColorTransfer @C.ColorTransfer
int configuredColorTransfer = defaultShaderProgram.getOutputColorTransfer(); int configuredColorTransfer = defaultShaderProgram.getOutputColorTransfer();
defaultShaderProgram.setOutputColorTransfer( defaultShaderProgram.setOutputColorTransfer(

View File

@ -35,6 +35,7 @@ import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.OnInputFrameProcessedListener; import androidx.media3.common.OnInputFrameProcessedListener;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.effect.DefaultVideoFrameProcessor.WorkingColorSpace;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -51,7 +52,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
private final GlShaderProgram.ErrorListener samplingShaderProgramErrorListener; private final GlShaderProgram.ErrorListener samplingShaderProgramErrorListener;
private final Executor errorListenerExecutor; private final Executor errorListenerExecutor;
private final SparseArray<Input> inputs; private final SparseArray<Input> inputs;
private final boolean enableColorTransfers; private final @WorkingColorSpace int sdrWorkingColorSpace;
private @MonotonicNonNull GlShaderProgram downstreamShaderProgram; private @MonotonicNonNull GlShaderProgram downstreamShaderProgram;
private @MonotonicNonNull TextureManager activeTextureManager; private @MonotonicNonNull TextureManager activeTextureManager;
@ -63,7 +64,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor, VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Executor errorListenerExecutor, Executor errorListenerExecutor,
GlShaderProgram.ErrorListener samplingShaderProgramErrorListener, GlShaderProgram.ErrorListener samplingShaderProgramErrorListener,
boolean enableColorTransfers, @WorkingColorSpace int sdrWorkingColorSpace,
boolean repeatLastRegisteredFrame) boolean repeatLastRegisteredFrame)
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
this.context = context; this.context = context;
@ -73,7 +74,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
this.errorListenerExecutor = errorListenerExecutor; this.errorListenerExecutor = errorListenerExecutor;
this.samplingShaderProgramErrorListener = samplingShaderProgramErrorListener; this.samplingShaderProgramErrorListener = samplingShaderProgramErrorListener;
this.inputs = new SparseArray<>(); this.inputs = new SparseArray<>();
this.enableColorTransfers = enableColorTransfers; this.sdrWorkingColorSpace = sdrWorkingColorSpace;
// TODO(b/274109008): Investigate lazy instantiating the texture managers. // TODO(b/274109008): Investigate lazy instantiating the texture managers.
inputs.put( inputs.put(
@ -98,13 +99,13 @@ import org.checkerframework.checker.nullness.qual.Nullable;
case INPUT_TYPE_SURFACE: case INPUT_TYPE_SURFACE:
samplingShaderProgram = samplingShaderProgram =
DefaultShaderProgram.createWithExternalSampler( DefaultShaderProgram.createWithExternalSampler(
context, inputColorInfo, outputColorInfo, enableColorTransfers); context, inputColorInfo, outputColorInfo, sdrWorkingColorSpace);
break; break;
case INPUT_TYPE_BITMAP: case INPUT_TYPE_BITMAP:
case INPUT_TYPE_TEXTURE_ID: case INPUT_TYPE_TEXTURE_ID:
samplingShaderProgram = samplingShaderProgram =
DefaultShaderProgram.createWithInternalSampler( DefaultShaderProgram.createWithInternalSampler(
context, inputColorInfo, outputColorInfo, enableColorTransfers, inputType); context, inputColorInfo, outputColorInfo, sdrWorkingColorSpace, inputType);
break; break;
default: default:
throw new VideoFrameProcessingException("Unsupported input type " + inputType); throw new VideoFrameProcessingException("Unsupported input type " + inputType);

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.transformer.mh; package androidx.media3.transformer.mh;
import static androidx.media3.effect.DefaultVideoFrameProcessor.WORKING_COLOR_SPACE_ORIGINAL;
import static androidx.media3.test.utils.TestUtil.retrieveTrackFormat; import static androidx.media3.test.utils.TestUtil.retrieveTrackFormat;
import static androidx.media3.transformer.AndroidTestUtil.FORCE_TRANSCODE_VIDEO_EFFECTS; import static androidx.media3.transformer.AndroidTestUtil.FORCE_TRANSCODE_VIDEO_EFFECTS;
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_5_SECOND_HLG10; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_5_SECOND_HLG10;
@ -210,7 +211,7 @@ public final class HdrEditingTest {
} }
@Test @Test
public void exportAndTranscodeHdr_withDisabledColorTransfers_whenHdrEditingIsSupported() public void exportAndTranscodeHdr_ignoringSdrWorkingColorSpace_whenHdrEditingIsSupported()
throws Exception { throws Exception {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Format format = MP4_ASSET_1080P_5_SECOND_HLG10_FORMAT; Format format = MP4_ASSET_1080P_5_SECOND_HLG10_FORMAT;
@ -222,7 +223,7 @@ public final class HdrEditingTest {
new Transformer.Builder(context) new Transformer.Builder(context)
.setVideoFrameProcessorFactory( .setVideoFrameProcessorFactory(
new DefaultVideoFrameProcessor.Factory.Builder() new DefaultVideoFrameProcessor.Factory.Builder()
.setEnableColorTransfers(false) .setSdrWorkingColorSpace(WORKING_COLOR_SPACE_ORIGINAL)
.build()) .build())
.build(); .build();
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =

View File

@ -16,6 +16,7 @@
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.effect.DefaultVideoFrameProcessor.WORKING_COLOR_SPACE_ORIGINAL;
import static androidx.media3.test.utils.BitmapPixelTestUtil.getBitmapAveragePixelAbsoluteDifferenceArgb8888; import static androidx.media3.test.utils.BitmapPixelTestUtil.getBitmapAveragePixelAbsoluteDifferenceArgb8888;
import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap; import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap;
import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped; import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped;
@ -280,14 +281,14 @@ public final class ToneMapHdrToSdrUsingOpenGlPixelTest {
} }
@Test @Test
public void toneMap_withDisabledColorTransfers_matchesGoldenFile() throws Exception { public void toneMap_withWorkingColorSpaceSetToOriginal_matchesGoldenFile() throws Exception {
assumeDeviceSupportsOpenGlToneMapping(testId, HLG_ASSET_FORMAT); assumeDeviceSupportsOpenGlToneMapping(testId, HLG_ASSET_FORMAT);
videoFrameProcessorTestRunner = videoFrameProcessorTestRunner =
new VideoFrameProcessorTestRunner.Builder() new VideoFrameProcessorTestRunner.Builder()
.setTestId(testId) .setTestId(testId)
.setVideoFrameProcessorFactory( .setVideoFrameProcessorFactory(
new DefaultVideoFrameProcessor.Factory.Builder() new DefaultVideoFrameProcessor.Factory.Builder()
.setEnableColorTransfers(false) .setSdrWorkingColorSpace(WORKING_COLOR_SPACE_ORIGINAL)
.build()) .build())
.setVideoAssetPath(HLG_ASSET_STRING) .setVideoAssetPath(HLG_ASSET_STRING)
.setOutputColorInfo(TONE_MAP_SDR_COLOR) .setOutputColorInfo(TONE_MAP_SDR_COLOR)