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 =
|
String fragmentShaderFilePath =
|
||||||
outputIsHdr
|
outputIsHdr
|
||||||
? FRAGMENT_SHADER_OETF_ES3_PATH
|
? FRAGMENT_SHADER_OETF_ES3_PATH
|
||||||
: FRAGMENT_SHADER_TRANSFORMATION_SDR_OETF_ES2_PATH;
|
: enableColorTransfers
|
||||||
if (!enableColorTransfers) {
|
? FRAGMENT_SHADER_TRANSFORMATION_SDR_OETF_ES2_PATH
|
||||||
fragmentShaderFilePath = FRAGMENT_SHADER_TRANSFORMATION_PATH;
|
: FRAGMENT_SHADER_TRANSFORMATION_PATH;
|
||||||
}
|
|
||||||
GlProgram glProgram = createGlProgram(context, vertexShaderFilePath, fragmentShaderFilePath);
|
GlProgram glProgram = createGlProgram(context, vertexShaderFilePath, fragmentShaderFilePath);
|
||||||
|
|
||||||
@C.ColorTransfer int outputColorTransfer = outputColorInfo.colorTransfer;
|
@C.ColorTransfer int outputColorTransfer = outputColorInfo.colorTransfer;
|
||||||
|
@ -113,7 +113,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
*
|
*
|
||||||
* <p>The default value is {@code true}.
|
* <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
|
* <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.
|
* {@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 List<Effect> activeEffects;
|
||||||
private final Object lock;
|
private final Object lock;
|
||||||
private final boolean enableColorTransfers;
|
|
||||||
private final ColorInfo outputColorInfo;
|
private final ColorInfo outputColorInfo;
|
||||||
|
|
||||||
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
|
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
|
||||||
@ -365,7 +365,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
Executor listenerExecutor,
|
Executor listenerExecutor,
|
||||||
FinalShaderProgramWrapper finalShaderProgramWrapper,
|
FinalShaderProgramWrapper finalShaderProgramWrapper,
|
||||||
boolean renderFramesAutomatically,
|
boolean renderFramesAutomatically,
|
||||||
boolean enableColorTransfers,
|
|
||||||
ColorInfo outputColorInfo) {
|
ColorInfo outputColorInfo) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.glObjectsProvider = glObjectsProvider;
|
this.glObjectsProvider = glObjectsProvider;
|
||||||
@ -378,7 +377,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
this.renderFramesAutomatically = renderFramesAutomatically;
|
this.renderFramesAutomatically = renderFramesAutomatically;
|
||||||
this.activeEffects = new ArrayList<>();
|
this.activeEffects = new ArrayList<>();
|
||||||
this.lock = new Object();
|
this.lock = new Object();
|
||||||
this.enableColorTransfers = enableColorTransfers;
|
|
||||||
this.outputColorInfo = outputColorInfo;
|
this.outputColorInfo = outputColorInfo;
|
||||||
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
|
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
|
||||||
this.intermediateGlShaderPrograms = new ArrayList<>();
|
this.intermediateGlShaderPrograms = new ArrayList<>();
|
||||||
@ -692,10 +690,14 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
.setColorTransfer(C.COLOR_TRANSFER_LINEAR)
|
.setColorTransfer(C.COLOR_TRANSFER_LINEAR)
|
||||||
.setHdrStaticInfo(null)
|
.setHdrStaticInfo(null)
|
||||||
.build();
|
.build();
|
||||||
|
ColorInfo intermediateColorInfo =
|
||||||
|
ColorInfo.isTransferHdr(outputColorInfo)
|
||||||
|
? linearColorInfo
|
||||||
|
: enableColorTransfers ? linearColorInfo : outputColorInfo;
|
||||||
InputSwitcher inputSwitcher =
|
InputSwitcher inputSwitcher =
|
||||||
new InputSwitcher(
|
new InputSwitcher(
|
||||||
context,
|
context,
|
||||||
/* outputColorInfo= */ linearColorInfo,
|
/* outputColorInfo= */ intermediateColorInfo,
|
||||||
glObjectsProvider,
|
glObjectsProvider,
|
||||||
videoFrameProcessingTaskExecutor,
|
videoFrameProcessingTaskExecutor,
|
||||||
/* errorListenerExecutor= */ videoFrameProcessorListenerExecutor,
|
/* errorListenerExecutor= */ videoFrameProcessorListenerExecutor,
|
||||||
@ -729,7 +731,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
videoFrameProcessorListenerExecutor,
|
videoFrameProcessorListenerExecutor,
|
||||||
finalShaderProgramWrapper,
|
finalShaderProgramWrapper,
|
||||||
renderFramesAutomatically,
|
renderFramesAutomatically,
|
||||||
enableColorTransfers,
|
|
||||||
outputColorInfo);
|
outputColorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -846,10 +847,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
*/
|
*/
|
||||||
private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure)
|
private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure)
|
||||||
throws VideoFrameProcessingException {
|
throws VideoFrameProcessingException {
|
||||||
checkColors(
|
checkColors(/* inputColorInfo= */ inputStreamInfo.frameInfo.colorInfo, outputColorInfo);
|
||||||
/* inputColorInfo= */ inputStreamInfo.frameInfo.colorInfo,
|
|
||||||
outputColorInfo,
|
|
||||||
enableColorTransfers);
|
|
||||||
if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) {
|
if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) {
|
||||||
if (!intermediateGlShaderPrograms.isEmpty()) {
|
if (!intermediateGlShaderPrograms.isEmpty()) {
|
||||||
for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) {
|
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}. */
|
/** Checks that color configuration is valid for {@link DefaultVideoFrameProcessor}. */
|
||||||
private static void checkColors(
|
private static void checkColors(ColorInfo inputColorInfo, ColorInfo outputColorInfo)
|
||||||
ColorInfo inputColorInfo, ColorInfo outputColorInfo, boolean enableColorTransfers)
|
|
||||||
throws VideoFrameProcessingException {
|
throws VideoFrameProcessingException {
|
||||||
if (ColorInfo.isTransferHdr(inputColorInfo)) {
|
if (ColorInfo.isTransferHdr(inputColorInfo)) {
|
||||||
checkArgument(inputColorInfo.colorSpace == C.COLOR_SPACE_BT2020);
|
checkArgument(inputColorInfo.colorSpace == C.COLOR_SPACE_BT2020);
|
||||||
}
|
}
|
||||||
if ((ColorInfo.isTransferHdr(inputColorInfo) || ColorInfo.isTransferHdr(outputColorInfo))) {
|
if ((ColorInfo.isTransferHdr(inputColorInfo) || ColorInfo.isTransferHdr(outputColorInfo))) {
|
||||||
checkArgument(enableColorTransfers);
|
|
||||||
long glVersion;
|
long glVersion;
|
||||||
try {
|
try {
|
||||||
glVersion = GlUtil.getContextMajorVersion();
|
glVersion = GlUtil.getContextMajorVersion();
|
||||||
|
@ -37,6 +37,7 @@ import androidx.media3.common.C;
|
|||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.effect.DefaultVideoFrameProcessor;
|
||||||
import androidx.media3.transformer.Composition;
|
import androidx.media3.transformer.Composition;
|
||||||
import androidx.media3.transformer.EditedMediaItem;
|
import androidx.media3.transformer.EditedMediaItem;
|
||||||
import androidx.media3.transformer.ExportException;
|
import androidx.media3.transformer.ExportException;
|
||||||
@ -208,6 +209,39 @@ public final class HdrEditingTest {
|
|||||||
assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG);
|
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
|
@Test
|
||||||
public void exportAndTranscode_hdr10File_whenHdrEditingUnsupported_toneMapsOrThrows()
|
public void exportAndTranscode_hdr10File_whenHdrEditingUnsupported_toneMapsOrThrows()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
@ -279,6 +279,46 @@ public final class ToneMapHdrToSdrUsingOpenGlPixelTest {
|
|||||||
.isAtMost(MAXIMUM_DEVICE_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
|
.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(
|
private static VideoFrameProcessorTestRunner.Builder getDefaultFrameProcessorTestRunnerBuilder(
|
||||||
String testId) {
|
String testId) {
|
||||||
return new VideoFrameProcessorTestRunner.Builder()
|
return new VideoFrameProcessorTestRunner.Builder()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user