diff --git a/libraries/common/src/main/java/androidx/media3/common/ColorInfo.java b/libraries/common/src/main/java/androidx/media3/common/ColorInfo.java index 0d7aa0778f..5d715c3989 100644 --- a/libraries/common/src/main/java/androidx/media3/common/ColorInfo.java +++ b/libraries/common/src/main/java/androidx/media3/common/ColorInfo.java @@ -134,7 +134,7 @@ public final class ColorInfo implements Bundleable { * Color info representing SDR sRGB in accordance with {@link * android.hardware.DataSpace#DATASPACE_SRGB}, which is a common SDR image color format. */ - public static final ColorInfo SRGB_FULL = + public static final ColorInfo SRGB_BT709_FULL = new ColorInfo.Builder() .setColorSpace(C.COLOR_SPACE_BT709) .setColorRange(C.COLOR_RANGE_FULL) diff --git a/libraries/effect/src/androidTest/java/androidx/media3/effect/DefaultVideoFrameProcessorImageFrameOutputTest.java b/libraries/effect/src/androidTest/java/androidx/media3/effect/DefaultVideoFrameProcessorImageFrameOutputTest.java index 718ec4b6b6..97bfded979 100644 --- a/libraries/effect/src/androidTest/java/androidx/media3/effect/DefaultVideoFrameProcessorImageFrameOutputTest.java +++ b/libraries/effect/src/androidTest/java/androidx/media3/effect/DefaultVideoFrameProcessorImageFrameOutputTest.java @@ -21,6 +21,7 @@ import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap; import static com.google.common.truth.Truth.assertThat; import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; import androidx.media3.test.utils.VideoFrameProcessorTestRunner; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.Queue; @@ -126,6 +127,7 @@ public class DefaultVideoFrameProcessorImageFrameOutputTest { .setTestId(testId) .setVideoFrameProcessorFactory(new DefaultVideoFrameProcessor.Factory.Builder().build()) .setInputType(INPUT_TYPE_BITMAP) + .setInputColorInfo(ColorInfo.SRGB_BT709_FULL) .setOnOutputFrameAvailableListener( unused -> checkNotNull(framesProduced).incrementAndGet()); } diff --git a/libraries/effect/src/androidTest/java/androidx/media3/effect/DefaultVideoFrameProcessorPixelTest.java b/libraries/effect/src/androidTest/java/androidx/media3/effect/DefaultVideoFrameProcessorPixelTest.java index b98f93deb1..d69712f0e2 100644 --- a/libraries/effect/src/androidTest/java/androidx/media3/effect/DefaultVideoFrameProcessorPixelTest.java +++ b/libraries/effect/src/androidTest/java/androidx/media3/effect/DefaultVideoFrameProcessorPixelTest.java @@ -27,6 +27,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Matrix; import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; import androidx.media3.common.Effect; import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.util.Size; @@ -111,7 +112,10 @@ public final class DefaultVideoFrameProcessorPixelTest { public void noEffects_withImageInput_matchesGoldenFile() throws Exception { String testId = "noEffects_withImageInput_matchesGoldenFile"; videoFrameProcessorTestRunner = - getDefaultFrameProcessorTestRunnerBuilder(testId).setInputType(INPUT_TYPE_BITMAP).build(); + getDefaultFrameProcessorTestRunnerBuilder(testId) + .setInputType(INPUT_TYPE_BITMAP) + .setInputColorInfo(ColorInfo.SRGB_BT709_FULL) + .build(); Bitmap originalBitmap = readBitmap(IMAGE_PNG_ASSET_PATH); Bitmap expectedBitmap = readBitmap(IMAGE_TO_VIDEO_PNG_ASSET_PATH); @@ -131,6 +135,7 @@ public final class DefaultVideoFrameProcessorPixelTest { videoFrameProcessorTestRunner = getDefaultFrameProcessorTestRunnerBuilder(testId) .setInputType(INPUT_TYPE_BITMAP) + .setInputColorInfo(ColorInfo.SRGB_BT709_FULL) .setEffects( new GlEffectWrapper( new Crop( diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ImageAssetLoader.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ImageAssetLoader.java index 5bdc9b88db..7e5563acb8 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ImageAssetLoader.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ImageAssetLoader.java @@ -29,6 +29,7 @@ import android.graphics.Bitmap; import android.os.Looper; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; import androidx.media3.common.Format; import androidx.media3.common.MediaItem; import androidx.media3.common.MimeTypes; @@ -115,6 +116,7 @@ public final class ImageAssetLoader implements AssetLoader { .setHeight(bitmap.getHeight()) .setWidth(bitmap.getWidth()) .setSampleMimeType(MIME_TYPE_IMAGE_ALL) + .setColorInfo(ColorInfo.SRGB_BT709_FULL) .build(); listener.onTrackAdded(format, SUPPORTED_OUTPUT_TYPE_DECODED); scheduledExecutorService.submit(() -> queueBitmapInternal(bitmap, format)); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java index e0ec2d74bf..5e4329cbc1 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java @@ -17,6 +17,7 @@ package androidx.media3.transformer; import static androidx.media3.common.ColorInfo.SDR_BT709_LIMITED; +import static androidx.media3.common.ColorInfo.SRGB_BT709_FULL; import static androidx.media3.common.ColorInfo.isTransferHdr; import static androidx.media3.common.VideoFrameProcessor.INPUT_TYPE_BITMAP; import static androidx.media3.common.VideoFrameProcessor.INPUT_TYPE_SURFACE; @@ -123,17 +124,25 @@ import org.checkerframework.dataflow.qual.Pure; boolean isGlToneMapping = ColorInfo.isTransferHdr(decoderInputColor) && transformationRequest.hdrMode == HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL; - // For consistency with the Android platform, OpenGL tone mapping outputs colors with - // C.COLOR_TRANSFER_GAMMA_2_2 instead of C.COLOR_TRANSFER_SDR, and outputs this as - // C.COLOR_TRANSFER_SDR to the encoder. - ColorInfo videoFrameProcessorOutputColor = - isGlToneMapping - ? new ColorInfo.Builder() - .setColorSpace(C.COLOR_SPACE_BT709) - .setColorRange(C.COLOR_RANGE_LIMITED) - .setColorTransfer(C.COLOR_TRANSFER_GAMMA_2_2) - .build() - : videoFrameProcessorInputColor; + ColorInfo videoFrameProcessorOutputColor; + if (videoFrameProcessorInputColor.colorTransfer == C.COLOR_TRANSFER_SRGB) { + // The sRGB color transfer is only used for images, so when an image gets transcoded into a + // video, we use the SMPTE 170M transfer function for the resulting video. + videoFrameProcessorOutputColor = SDR_BT709_LIMITED; + } else if (isGlToneMapping) { + // For consistency with the Android platform, OpenGL tone mapping outputs colors with + // C.COLOR_TRANSFER_GAMMA_2_2 instead of C.COLOR_TRANSFER_SDR, and outputs this as + // C.COLOR_TRANSFER_SDR to the encoder. + videoFrameProcessorOutputColor = + new ColorInfo.Builder() + .setColorSpace(C.COLOR_SPACE_BT709) + .setColorRange(C.COLOR_RANGE_LIMITED) + .setColorTransfer(C.COLOR_TRANSFER_GAMMA_2_2) + .build(); + } else { + videoFrameProcessorOutputColor = videoFrameProcessorInputColor; + } + List effectsWithPresentation = new ArrayList<>(effects); if (presentation != null) { effectsWithPresentation.add(presentation); @@ -409,6 +418,9 @@ import org.checkerframework.dataflow.qual.Pure; // populate default color info, which depends on the resolution. return ColorInfo.SDR_BT709_LIMITED; } + if (SRGB_BT709_FULL.equals(inputFormat.colorInfo)) { + return ColorInfo.SDR_BT709_LIMITED; + } return checkNotNull(inputFormat.colorInfo); }