Effect: Allow updating inputColorInfo between streams in VFP.

Whenever the inputColorInfo updates, update the samplingGlShaderProgram.

Also, allow either SDR or gamma2.2 to be used for HDR->SDR tone-mapping
`outputColorInfo` request. This is required because we can't update the
`outputColorInfo`, but plan to always use gamma2.2 for `outputColorInfo` in the
future.

This allows VideoFrameProcessor to work as is for exoplayer previewing, but
only when not seeking. As we haven't plumbed the per-stream inputColorInfo from
ExoPlayer down to VFP.registerInputStream, follow-up CLs will be needed to
properly support previewing with changing inputColorInfo.

PiperOrigin-RevId: 596627890
This commit is contained in:
huangdarwin 2024-01-08 10:06:10 -08:00 committed by Copybara-Service
parent 77f311917f
commit c6b51003d3
3 changed files with 40 additions and 25 deletions

View File

@ -379,8 +379,12 @@ import java.util.List;
glProgram.setIntUniform( glProgram.setIntUniform(
"uApplyHdrToSdrToneMapping", "uApplyHdrToSdrToneMapping",
/* value= */ (outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020) ? GL_TRUE : GL_FALSE); /* value= */ (outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020) ? GL_TRUE : GL_FALSE);
checkArgument( checkArgument(outputColorTransfer != Format.NO_VALUE);
outputColorTransfer != Format.NO_VALUE && outputColorTransfer != C.COLOR_TRANSFER_SDR); if (outputColorTransfer == C.COLOR_TRANSFER_SDR) {
// When tone-mapping from HDR to SDR, COLOR_TRANSFER_SDR is interpreted as
// COLOR_TRANSFER_GAMMA_2_2.
outputColorTransfer = C.COLOR_TRANSFER_GAMMA_2_2;
}
glProgram.setIntUniform("uOutputColorTransfer", outputColorTransfer); glProgram.setIntUniform("uOutputColorTransfer", outputColorTransfer);
} else { } else {
glProgram.setIntUniform("uEnableColorTransfer", enableColorTransfers ? GL_TRUE : GL_FALSE); glProgram.setIntUniform("uEnableColorTransfer", enableColorTransfers ? GL_TRUE : GL_FALSE);

View File

@ -310,8 +310,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private final boolean enableColorTransfers; private final boolean enableColorTransfers;
private final ColorInfo outputColorInfo; private final ColorInfo outputColorInfo;
private @MonotonicNonNull ColorInfo firstInputColorInfo;
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo; private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
private volatile boolean inputStreamEnded; private volatile boolean inputStreamEnded;
@ -438,6 +436,14 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
* GLES30#GL_HALF_FLOAT}. Otherwise, textures will use {@link GLES20#GL_RGBA} and {@link * GLES30#GL_HALF_FLOAT}. Otherwise, textures will use {@link GLES20#GL_RGBA} and {@link
* GLES20#GL_UNSIGNED_BYTE}. * GLES20#GL_UNSIGNED_BYTE}.
* *
* <p>If {@linkplain FrameInfo#colorInfo input color} {@linkplain ColorInfo#isTransferHdr is HDR},
* but {@code outputColorInfo} is SDR, then HDR to SDR tone-mapping is applied, and {@code
* outputColorInfo}'s {@link ColorInfo#colorTransfer} must be {@link C#COLOR_TRANSFER_GAMMA_2_2}
* or {@link C#COLOR_TRANSFER_SDR}. In this case, the actual output transfer function will be in
* {@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
* SMPTE 170M, but most OEMs process it as Gamma 2.2).
*
* <p>If either {@link FrameInfo#colorInfo} or {@code outputColorInfo} {@linkplain * <p>If either {@link FrameInfo#colorInfo} or {@code outputColorInfo} {@linkplain
* ColorInfo#isTransferHdr} are HDR}, color transfers must {@linkplain * ColorInfo#isTransferHdr} are HDR}, color transfers must {@linkplain
* Factory.Builder#setEnableColorTransfers be enabled}. * Factory.Builder#setEnableColorTransfers be enabled}.
@ -445,7 +451,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
* <p>The {@link FrameInfo}'s {@link ColorInfo} must not change between different calls to this * <p>The {@link FrameInfo}'s {@link ColorInfo} must not change between different calls to this
* method. * method.
*/ */
// TODO: b/307952514: After FrameInfo.colorInfo may change between calls, remove relevant javadoc. // TODO: b/307952514: After updating frameInfo.colorInfo works with flushing, remove relevant
// javadoc.
@Override @Override
public void registerInputStream( public void registerInputStream(
@InputType int inputType, List<Effect> effects, FrameInfo frameInfo) { @InputType int inputType, List<Effect> effects, FrameInfo frameInfo) {
@ -805,14 +812,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
*/ */
private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure) private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure)
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
// TODO: b/307952514 - Remove this color check, and reinitialize the InputSwitcher's
// samplingGlShaderProgram instead.
checkState(
firstInputColorInfo == null
|| firstInputColorInfo.equals(inputStreamInfo.frameInfo.colorInfo));
if (inputStreamInfo.frameInfo.colorInfo != null) {
firstInputColorInfo = inputStreamInfo.frameInfo.colorInfo;
}
checkColors( checkColors(
/* inputColorInfo= */ inputStreamInfo.frameInfo.colorInfo, /* inputColorInfo= */ inputStreamInfo.frameInfo.colorInfo,
outputColorInfo, outputColorInfo,
@ -877,14 +876,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
if (inputColorInfo.colorSpace != outputColorInfo.colorSpace if (inputColorInfo.colorSpace != outputColorInfo.colorSpace
|| ColorInfo.isTransferHdr(inputColorInfo) != ColorInfo.isTransferHdr(outputColorInfo)) { || ColorInfo.isTransferHdr(inputColorInfo) != ColorInfo.isTransferHdr(outputColorInfo)) {
// OpenGL tone mapping is only implemented for BT2020 to BT709 and HDR to SDR (Gamma 2.2). // OpenGL tone mapping is only implemented for BT2020 to BT709 and HDR to SDR.
// Gamma 2.2 is used instead of SMPTE 170M for SDR, despite MediaFormat's
// COLOR_TRANSFER_SDR_VIDEO being defined as SMPTE 170M. This is to match
// other known tone-mapping behavior within the Android ecosystem.
checkArgument(inputColorInfo.colorSpace == C.COLOR_SPACE_BT2020); checkArgument(inputColorInfo.colorSpace == C.COLOR_SPACE_BT2020);
checkArgument(outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020); checkArgument(outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020);
checkArgument(ColorInfo.isTransferHdr(inputColorInfo)); checkArgument(ColorInfo.isTransferHdr(inputColorInfo));
checkArgument(outputColorInfo.colorTransfer == C.COLOR_TRANSFER_GAMMA_2_2); checkArgument(
outputColorInfo.colorTransfer == C.COLOR_TRANSFER_GAMMA_2_2
|| outputColorInfo.colorTransfer == C.COLOR_TRANSFER_SDR);
} }
} }

View File

@ -154,7 +154,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* @param inputFrameInfo The {@link FrameInfo} associated with the new input. * @param inputFrameInfo The {@link FrameInfo} associated with the new input.
*/ */
public void switchToInput( public void switchToInput(
@VideoFrameProcessor.InputType int newInputType, FrameInfo inputFrameInfo) @VideoFrameProcessor.InputType int newInputType, FrameInfo newInputFrameInfo)
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
checkStateNotNull(downstreamShaderProgram); checkStateNotNull(downstreamShaderProgram);
checkState(contains(inputs, newInputType), "Input type not registered: " + newInputType); checkState(contains(inputs, newInputType), "Input type not registered: " + newInputType);
@ -163,11 +163,11 @@ import org.checkerframework.checker.nullness.qual.Nullable;
@VideoFrameProcessor.InputType int inputType = inputs.keyAt(i); @VideoFrameProcessor.InputType int inputType = inputs.keyAt(i);
Input input = inputs.get(inputType); Input input = inputs.get(inputType);
if (inputType == newInputType) { if (inputType == newInputType) {
if (input.getSamplingGlShaderProgram() == null) { if (input.getInputColorInfo() == null
// TODO: b/307952514 - When switchToInput is called, and the inputColorInfo doesn't match || !newInputFrameInfo.colorInfo.equals(input.getInputColorInfo())) {
// the prior inputColorInfo, recreate and reinitialize the input.samplingGlShaderProgram.
input.setSamplingGlShaderProgram( input.setSamplingGlShaderProgram(
createSamplingShaderProgram(inputFrameInfo.colorInfo, newInputType)); createSamplingShaderProgram(newInputFrameInfo.colorInfo, newInputType));
input.setInputColorInfo(newInputFrameInfo.colorInfo);
} }
input.setChainingListener( input.setChainingListener(
new GatedChainingListenerWrapper( new GatedChainingListenerWrapper(
@ -182,7 +182,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
input.setActive(false); input.setActive(false);
} }
} }
checkNotNull(activeTextureManager).setInputFrameInfo(inputFrameInfo); checkNotNull(activeTextureManager).setInputFrameInfo(newInputFrameInfo);
} }
/** Returns whether the {@code InputSwitcher} is connected to an active input. */ /** Returns whether the {@code InputSwitcher} is connected to an active input. */
@ -248,18 +248,27 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public final TextureManager textureManager; public final TextureManager textureManager;
private @MonotonicNonNull ExternalShaderProgram samplingGlShaderProgram; private @MonotonicNonNull ExternalShaderProgram samplingGlShaderProgram;
private @MonotonicNonNull ColorInfo inputColorInfo;
private @MonotonicNonNull GatedChainingListenerWrapper gatedChainingListenerWrapper; private @MonotonicNonNull GatedChainingListenerWrapper gatedChainingListenerWrapper;
public Input(TextureManager textureManager) { public Input(TextureManager textureManager) {
this.textureManager = textureManager; this.textureManager = textureManager;
} }
public void setSamplingGlShaderProgram(ExternalShaderProgram samplingGlShaderProgram) { public void setSamplingGlShaderProgram(ExternalShaderProgram samplingGlShaderProgram)
throws VideoFrameProcessingException {
if (this.samplingGlShaderProgram != null) {
this.samplingGlShaderProgram.release();
}
this.samplingGlShaderProgram = samplingGlShaderProgram; this.samplingGlShaderProgram = samplingGlShaderProgram;
textureManager.setSamplingGlShaderProgram(samplingGlShaderProgram); textureManager.setSamplingGlShaderProgram(samplingGlShaderProgram);
samplingGlShaderProgram.setInputListener(textureManager); samplingGlShaderProgram.setInputListener(textureManager);
} }
public void setInputColorInfo(ColorInfo inputColorInfo) {
this.inputColorInfo = inputColorInfo;
}
public void setChainingListener(GatedChainingListenerWrapper gatedChainingListenerWrapper) { public void setChainingListener(GatedChainingListenerWrapper gatedChainingListenerWrapper) {
this.gatedChainingListenerWrapper = gatedChainingListenerWrapper; this.gatedChainingListenerWrapper = gatedChainingListenerWrapper;
checkNotNull(samplingGlShaderProgram).setOutputListener(gatedChainingListenerWrapper); checkNotNull(samplingGlShaderProgram).setOutputListener(gatedChainingListenerWrapper);
@ -269,6 +278,10 @@ import org.checkerframework.checker.nullness.qual.Nullable;
return samplingGlShaderProgram; return samplingGlShaderProgram;
} }
public @Nullable ColorInfo getInputColorInfo() {
return inputColorInfo;
}
public void setActive(boolean active) { public void setActive(boolean active) {
if (gatedChainingListenerWrapper == null) { if (gatedChainingListenerWrapper == null) {
return; return;