Update VideoSamplePipeline to take account of decoder colors

PiperOrigin-RevId: 526940261
This commit is contained in:
tofunmi 2023-04-25 13:47:29 +01:00 committed by Ian Baker
parent c539cb8575
commit 42c9c28daf
2 changed files with 46 additions and 35 deletions

View File

@ -16,9 +16,11 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.common.ColorInfo.SDR_BT709_LIMITED;
import static androidx.media3.common.ColorInfo.isTransferHdr; import static androidx.media3.common.ColorInfo.isTransferHdr;
import static androidx.media3.common.VideoFrameProcessor.INPUT_TYPE_BITMAP; import static androidx.media3.common.VideoFrameProcessor.INPUT_TYPE_BITMAP;
import static androidx.media3.common.VideoFrameProcessor.INPUT_TYPE_SURFACE; import static androidx.media3.common.VideoFrameProcessor.INPUT_TYPE_SURFACE;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.transformer.EncoderUtil.getSupportedEncodersForHdrEditing; import static androidx.media3.transformer.EncoderUtil.getSupportedEncodersForHdrEditing;
import static androidx.media3.transformer.TransformationRequest.HDR_MODE_KEEP_HDR; import static androidx.media3.transformer.TransformationRequest.HDR_MODE_KEEP_HDR;
@ -59,9 +61,7 @@ import org.checkerframework.dataflow.qual.Pure;
/** Pipeline to process, re-encode and mux raw video frames. */ /** Pipeline to process, re-encode and mux raw video frames. */
/* package */ final class VideoSamplePipeline extends SamplePipeline { /* package */ final class VideoSamplePipeline extends SamplePipeline {
/** MIME type to use for output video if the input type is not a video. */ private static final String TAG = "VideoSamplePipeline";
private static final String DEFAULT_OUTPUT_MIME_TYPE = MimeTypes.VIDEO_H265;
private final AtomicLong mediaItemOffsetUs; private final AtomicLong mediaItemOffsetUs;
private final VideoFrameProcessor videoFrameProcessor; private final VideoFrameProcessor videoFrameProcessor;
private final ColorInfo videoFrameProcessorInputColor; private final ColorInfo videoFrameProcessorInputColor;
@ -99,22 +99,30 @@ import org.checkerframework.dataflow.qual.Pure;
encoderOutputBuffer = encoderOutputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
ColorInfo decoderInputColor;
if (firstInputFormat.colorInfo == null || !firstInputFormat.colorInfo.isValid()) {
Log.d(TAG, "colorInfo is null or invalid. Defaulting to SDR_BT709_LIMITED.");
decoderInputColor = ColorInfo.SDR_BT709_LIMITED;
} else {
decoderInputColor = firstInputFormat.colorInfo;
}
encoderWrapper = encoderWrapper =
new EncoderWrapper( new EncoderWrapper(
encoderFactory, encoderFactory,
firstInputFormat, firstInputFormat.buildUpon().setColorInfo(decoderInputColor).build(),
muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_VIDEO), muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_VIDEO),
transformationRequest, transformationRequest,
fallbackListener); fallbackListener);
ColorInfo encoderInputColor = encoderWrapper.getSupportedInputColor(); boolean isMediaCodecToneMapping =
// If not tone mapping using OpenGL, the decoder will output the encoderInputColor, encoderWrapper.getSupportedInputHdrMode() == HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC
// possibly by tone mapping. && ColorInfo.isTransferHdr(decoderInputColor);
videoFrameProcessorInputColor = isMediaCodecToneMapping ? SDR_BT709_LIMITED : decoderInputColor;
boolean isGlToneMapping = boolean isGlToneMapping =
ColorInfo.isTransferHdr(firstInputFormat.colorInfo) ColorInfo.isTransferHdr(decoderInputColor)
&& transformationRequest.hdrMode == HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL; && transformationRequest.hdrMode == HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL;
videoFrameProcessorInputColor =
isGlToneMapping ? checkNotNull(firstInputFormat.colorInfo) : encoderInputColor;
// For consistency with the Android platform, OpenGL tone mapping outputs colors with // 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_GAMMA_2_2 instead of C.COLOR_TRANSFER_SDR, and outputs this as
// C.COLOR_TRANSFER_SDR to the encoder. // C.COLOR_TRANSFER_SDR to the encoder.
@ -125,7 +133,7 @@ import org.checkerframework.dataflow.qual.Pure;
.setColorRange(C.COLOR_RANGE_LIMITED) .setColorRange(C.COLOR_RANGE_LIMITED)
.setColorTransfer(C.COLOR_TRANSFER_GAMMA_2_2) .setColorTransfer(C.COLOR_TRANSFER_GAMMA_2_2)
.build() .build()
: encoderInputColor; : videoFrameProcessorInputColor;
List<Effect> effectsWithPresentation = new ArrayList<>(effects); List<Effect> effectsWithPresentation = new ArrayList<>(effects);
if (presentation != null) { if (presentation != null) {
effectsWithPresentation.add(presentation); effectsWithPresentation.add(presentation);
@ -339,7 +347,8 @@ import org.checkerframework.dataflow.qual.Pure;
*/ */
@VisibleForTesting @VisibleForTesting
/* package */ static final class EncoderWrapper { /* package */ static final class EncoderWrapper {
private static final String TAG = "EncoderWrapper"; /** MIME type to use for output video if the input type is not a video. */
private static final String DEFAULT_OUTPUT_MIME_TYPE = MimeTypes.VIDEO_H265;
private final Codec.EncoderFactory encoderFactory; private final Codec.EncoderFactory encoderFactory;
private final Format inputFormat; private final Format inputFormat;
@ -347,7 +356,8 @@ import org.checkerframework.dataflow.qual.Pure;
private final TransformationRequest transformationRequest; private final TransformationRequest transformationRequest;
private final FallbackListener fallbackListener; private final FallbackListener fallbackListener;
private final String requestedOutputMimeType; private final String requestedOutputMimeType;
private final boolean isHdrEditingEnabled; private final boolean isInputToneMapped;
private final @TransformationRequest.HdrMode int supportedFallbackHdrMode;
private @MonotonicNonNull SurfaceInfo encoderSurfaceInfo; private @MonotonicNonNull SurfaceInfo encoderSurfaceInfo;
@ -361,6 +371,7 @@ import org.checkerframework.dataflow.qual.Pure;
List<String> muxerSupportedMimeTypes, List<String> muxerSupportedMimeTypes,
TransformationRequest transformationRequest, TransformationRequest transformationRequest,
FallbackListener fallbackListener) { FallbackListener fallbackListener) {
checkArgument(inputFormat.colorInfo != null);
this.encoderFactory = encoderFactory; this.encoderFactory = encoderFactory;
this.inputFormat = inputFormat; this.inputFormat = inputFormat;
this.muxerSupportedMimeTypes = muxerSupportedMimeTypes; this.muxerSupportedMimeTypes = muxerSupportedMimeTypes;
@ -376,26 +387,33 @@ import org.checkerframework.dataflow.qual.Pure;
requestedOutputMimeType = inputSampleMimeType; requestedOutputMimeType = inputSampleMimeType;
} }
isHdrEditingEnabled = isInputToneMapped =
transformationRequest.hdrMode == HDR_MODE_KEEP_HDR isTransferHdr(inputFormat.colorInfo)
&& !getSupportedEncodersForHdrEditing(requestedOutputMimeType, inputFormat.colorInfo) && getSupportedEncodersForHdrEditing(requestedOutputMimeType, inputFormat.colorInfo)
.isEmpty(); .isEmpty();
// HdrMode fallback is only supported from HDR_MODE_KEEP_HDR to
// HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC.
boolean fallbackToMediaCodec =
isInputToneMapped && transformationRequest.hdrMode == HDR_MODE_KEEP_HDR;
supportedFallbackHdrMode =
fallbackToMediaCodec
? HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC
: transformationRequest.hdrMode;
} }
/** Returns the {@link ColorInfo} expected from the input surface. */ /** Returns the {@link ColorInfo} expected from the input surface. */
public ColorInfo getSupportedInputColor() { public ColorInfo getSupportedInputColor() {
boolean isInputToneMapped = !isHdrEditingEnabled && isTransferHdr(inputFormat.colorInfo);
if (isInputToneMapped) { if (isInputToneMapped) {
// When tone-mapping HDR to SDR is enabled, assume we get BT.709 to avoid having the encoder // When tone-mapping HDR to SDR is enabled, assume we get BT.709 to avoid having the encoder
// populate default color info, which depends on the resolution. // populate default color info, which depends on the resolution.
return ColorInfo.SDR_BT709_LIMITED; return ColorInfo.SDR_BT709_LIMITED;
} }
if (inputFormat.colorInfo == null || !inputFormat.colorInfo.isValid()) { return checkNotNull(inputFormat.colorInfo);
Log.d(TAG, "colorInfo is null or invalid. Defaulting to SDR_BT709_LIMITED.");
return ColorInfo.SDR_BT709_LIMITED;
} }
return inputFormat.colorInfo;
public @TransformationRequest.HdrMode int getSupportedInputHdrMode() {
return supportedFallbackHdrMode;
} }
@Nullable @Nullable
@ -440,17 +458,6 @@ import org.checkerframework.dataflow.qual.Pure;
Format actualEncoderFormat = encoder.getConfigurationFormat(); Format actualEncoderFormat = encoder.getConfigurationFormat();
boolean isInputToneMapped =
isTransferHdr(inputFormat.colorInfo) && !isTransferHdr(requestedEncoderFormat.colorInfo);
// HdrMode fallback is only supported from HDR_MODE_KEEP_HDR to
// HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC.
@TransformationRequest.HdrMode
int supportedFallbackHdrMode =
isInputToneMapped && transformationRequest.hdrMode == HDR_MODE_KEEP_HDR
? HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC
: transformationRequest.hdrMode;
fallbackListener.onTransformationRequestFinalized( fallbackListener.onTransformationRequestFinalized(
createSupportedTransformationRequest( createSupportedTransformationRequest(
transformationRequest, transformationRequest,

View File

@ -24,6 +24,7 @@ import android.media.MediaFormat;
import android.net.Uri; import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
@ -63,7 +64,10 @@ public final class VideoEncoderWrapperTest {
private final VideoSamplePipeline.EncoderWrapper encoderWrapper = private final VideoSamplePipeline.EncoderWrapper encoderWrapper =
new VideoSamplePipeline.EncoderWrapper( new VideoSamplePipeline.EncoderWrapper(
fakeEncoderFactory, fakeEncoderFactory,
/* inputFormat= */ new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build(), /* inputFormat= */ new Format.Builder()
.setSampleMimeType(MimeTypes.VIDEO_H264)
.setColorInfo(ColorInfo.SDR_BT709_LIMITED)
.build(),
/* muxerSupportedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264), /* muxerSupportedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264),
emptyTransformationRequest, emptyTransformationRequest,
fallbackListener); fallbackListener);