HDR: Put force HDR as SDR into AssetLoader

This allows us to fix usage of HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR.

Before, this was checked in the VideoSamplePipeline, which no longer decides on the decoder configuration input format.

PiperOrigin-RevId: 510142097
This commit is contained in:
huangdarwin 2023-02-16 15:49:19 +00:00 committed by christosts
parent 221c5afb1b
commit fb8bbce5f1
8 changed files with 91 additions and 31 deletions

View File

@ -34,6 +34,7 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory {
private final Context context;
private final Codec.DecoderFactory decoderFactory;
private final boolean forceInterpretHdrAsSdr;
private final Clock clock;
private final MediaSource.@MonotonicNonNull Factory mediaSourceFactory;
@ -45,13 +46,19 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory {
* @param context The {@link Context}.
* @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
* necessary).
* @param forceInterpretHdrAsSdr Whether to apply {@link
* TransformationRequest#HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}.
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
* testing.
*/
public DefaultAssetLoaderFactory(
Context context, Codec.DecoderFactory decoderFactory, Clock clock) {
Context context,
Codec.DecoderFactory decoderFactory,
boolean forceInterpretHdrAsSdr,
Clock clock) {
this.context = context;
this.decoderFactory = decoderFactory;
this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr;
this.clock = clock;
this.mediaSourceFactory = null;
}
@ -62,6 +69,8 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory {
* @param context The {@link Context}.
* @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
* necessary).
* @param forceInterpretHdrAsSdr Whether to apply {@link
* TransformationRequest#HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}.
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
* testing.
* @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to
@ -70,10 +79,12 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory {
public DefaultAssetLoaderFactory(
Context context,
Codec.DecoderFactory decoderFactory,
boolean forceInterpretHdrAsSdr,
Clock clock,
MediaSource.Factory mediaSourceFactory) {
this.context = context;
this.decoderFactory = decoderFactory;
this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr;
this.clock = clock;
this.mediaSourceFactory = mediaSourceFactory;
}
@ -91,8 +102,10 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory {
if (exoPlayerAssetLoaderFactory == null) {
exoPlayerAssetLoaderFactory =
mediaSourceFactory != null
? new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory)
: new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock);
? new ExoPlayerAssetLoader.Factory(
context, decoderFactory, forceInterpretHdrAsSdr, clock, mediaSourceFactory)
: new ExoPlayerAssetLoader.Factory(
context, decoderFactory, forceInterpretHdrAsSdr, clock);
}
return exoPlayerAssetLoaderFactory.createAssetLoader(editedMediaItem, looper, listener);
}

View File

@ -135,6 +135,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
}
}
/** Overrides the {@code inputFormat}. */
protected Format overrideFormat(Format inputFormat) throws ExportException {
return inputFormat;
}
/** Called when the {@link Format} of the samples fed to the renderer is known. */
protected void onInputFormatRead(Format inputFormat) {}
@ -176,7 +181,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
if (result != C.RESULT_FORMAT_READ) {
return false;
}
Format inputFormat = checkNotNull(formatHolder.format);
Format inputFormat = overrideFormat(checkNotNull(formatHolder.format));
@AssetLoader.SupportedOutputTypes
int supportedOutputTypes = SUPPORTED_OUTPUT_TYPE_ENCODED | SUPPORTED_OUTPUT_TYPE_DECODED;
sampleConsumer =

View File

@ -16,6 +16,7 @@
package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.SDK_INT;
import android.media.MediaCodec;
import androidx.annotation.Nullable;
@ -35,6 +36,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final boolean flattenForSlowMotion;
private final Codec.DecoderFactory decoderFactory;
private final boolean forceInterpretHdrAsSdr;
private final List<Long> decodeOnlyPresentationTimestamps;
private @MonotonicNonNull SefSlowMotionFlattener sefVideoSlowMotionFlattener;
@ -43,11 +45,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public ExoAssetLoaderVideoRenderer(
boolean flattenForSlowMotion,
Codec.DecoderFactory decoderFactory,
boolean forceInterpretHdrAsSdr,
TransformerMediaClock mediaClock,
AssetLoader.Listener assetLoaderListener) {
super(C.TRACK_TYPE_VIDEO, mediaClock, assetLoaderListener);
this.flattenForSlowMotion = flattenForSlowMotion;
this.decoderFactory = decoderFactory;
this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr;
decodeOnlyPresentationTimestamps = new ArrayList<>();
}
@ -56,6 +60,23 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return TAG;
}
@Override
protected Format overrideFormat(Format inputFormat) throws ExportException {
if (forceInterpretHdrAsSdr && ColorInfo.isTransferHdr(inputFormat.colorInfo)) {
if (SDK_INT < 29) {
throw ExportException.createForCodec(
new IllegalArgumentException(
"Interpreting HDR video as SDR is not supported on this device."),
ExportException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED,
/* isVideo= */ true,
/* isDecoder= */ true,
inputFormat);
}
return inputFormat.buildUpon().setColorInfo(ColorInfo.SDR_BT709_LIMITED).build();
}
return inputFormat;
}
@Override
protected void onInputFormatRead(Format inputFormat) {
if (flattenForSlowMotion) {

View File

@ -64,6 +64,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
private final Context context;
private final Codec.DecoderFactory decoderFactory;
private final boolean forceInterpretHdrAsSdr;
private final Clock clock;
@Nullable private final MediaSource.Factory mediaSourceFactory;
@ -73,12 +74,19 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
* @param context The {@link Context}.
* @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
* necessary).
* @param forceInterpretHdrAsSdr Whether to apply {@link
* TransformationRequest#HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}.
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
* testing.
*/
public Factory(Context context, Codec.DecoderFactory decoderFactory, Clock clock) {
public Factory(
Context context,
Codec.DecoderFactory decoderFactory,
boolean forceInterpretHdrAsSdr,
Clock clock) {
this.context = context;
this.decoderFactory = decoderFactory;
this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr;
this.clock = clock;
this.mediaSourceFactory = null;
}
@ -89,6 +97,8 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
* @param context The {@link Context}.
* @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
* necessary).
* @param forceInterpretHdrAsSdr Whether to apply {@link
* TransformationRequest#HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}.
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
* testing.
* @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to
@ -97,10 +107,12 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
public Factory(
Context context,
Codec.DecoderFactory decoderFactory,
boolean forceInterpretHdrAsSdr,
Clock clock,
MediaSource.Factory mediaSourceFactory) {
this.context = context;
this.decoderFactory = decoderFactory;
this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr;
this.clock = clock;
this.mediaSourceFactory = mediaSourceFactory;
}
@ -117,7 +129,14 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory);
}
return new ExoPlayerAssetLoader(
context, editedMediaItem, mediaSourceFactory, decoderFactory, looper, listener, clock);
context,
editedMediaItem,
mediaSourceFactory,
decoderFactory,
forceInterpretHdrAsSdr,
looper,
listener,
clock);
}
}
@ -132,6 +151,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
EditedMediaItem editedMediaItem,
MediaSource.Factory mediaSourceFactory,
Codec.DecoderFactory decoderFactory,
boolean forceInterpretHdrAsSdr,
Looper looper,
Listener listener,
Clock clock) {
@ -161,6 +181,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
editedMediaItem.removeVideo,
editedMediaItem.flattenForSlowMotion,
this.decoderFactory,
forceInterpretHdrAsSdr,
listener))
.setMediaSourceFactory(mediaSourceFactory)
.setTrackSelector(trackSelector)
@ -223,6 +244,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
private final boolean removeVideo;
private final boolean flattenForSlowMotion;
private final Codec.DecoderFactory decoderFactory;
private final boolean forceInterpretHdrAsSdr;
private final Listener assetLoaderListener;
public RenderersFactoryImpl(
@ -230,11 +252,13 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
boolean removeVideo,
boolean flattenForSlowMotion,
Codec.DecoderFactory decoderFactory,
boolean forceInterpretHdrAsSdr,
Listener assetLoaderListener) {
this.removeAudio = removeAudio;
this.removeVideo = removeVideo;
this.flattenForSlowMotion = flattenForSlowMotion;
this.decoderFactory = decoderFactory;
this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr;
this.assetLoaderListener = assetLoaderListener;
mediaClock = new TransformerMediaClock();
}
@ -257,7 +281,11 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
if (!removeVideo) {
renderers[index] =
new ExoAssetLoaderVideoRenderer(
flattenForSlowMotion, decoderFactory, mediaClock, assetLoaderListener);
flattenForSlowMotion,
decoderFactory,
forceInterpretHdrAsSdr,
mediaClock,
assetLoaderListener);
index++;
}
return renderers;

View File

@ -437,7 +437,12 @@ public final class Transformer {
}
if (assetLoaderFactory == null) {
assetLoaderFactory =
new DefaultAssetLoaderFactory(context, new DefaultDecoderFactory(context), clock);
new DefaultAssetLoaderFactory(
context,
new DefaultDecoderFactory(context),
/* forceInterpretHdrAsSdr= */ transformationRequest.hdrMode
== TransformationRequest.HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR,
clock);
}
return new Transformer(
context,

View File

@ -18,9 +18,7 @@ package androidx.media3.transformer;
import static androidx.media3.common.ColorInfo.isTransferHdr;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.transformer.EncoderUtil.getSupportedEncodersForHdrEditing;
import static androidx.media3.transformer.TransformationRequest.HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR;
import static androidx.media3.transformer.TransformationRequest.HDR_MODE_KEEP_HDR;
import static androidx.media3.transformer.TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC;
import static androidx.media3.transformer.TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL;
@ -87,25 +85,6 @@ import org.checkerframework.dataflow.qual.Pure;
throws ExportException {
super(firstInputFormat, streamStartPositionUs, muxerWrapper);
boolean isGlToneMapping = false;
if (isTransferHdr(firstInputFormat.colorInfo)) {
if (transformationRequest.hdrMode == HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR) {
if (SDK_INT < 29) {
throw ExportException.createForCodec(
new IllegalArgumentException(
"Interpreting HDR video as SDR is not supported on this device."),
ExportException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED,
/* isVideo= */ true,
/* isDecoder= */ true,
firstInputFormat);
}
firstInputFormat =
firstInputFormat.buildUpon().setColorInfo(ColorInfo.SDR_BT709_LIMITED).build();
} else if (transformationRequest.hdrMode == HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL) {
isGlToneMapping = true;
}
}
finalFramePresentationTimeUs = C.TIME_UNSET;
encoderOutputBuffer =
@ -122,6 +101,9 @@ import org.checkerframework.dataflow.qual.Pure;
ColorInfo encoderInputColor = encoderWrapper.getSupportedInputColor();
// If not tone mapping using OpenGL, the decoder will output the encoderInputColor,
// possibly by tone mapping.
boolean isGlToneMapping =
ColorInfo.isTransferHdr(firstInputFormat.colorInfo)
&& 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

View File

@ -119,7 +119,8 @@ public class ExoPlayerAssetLoaderTest {
Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri("asset:///media/mp4/sample.mp4")).build();
return new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock)
return new ExoPlayerAssetLoader.Factory(
context, decoderFactory, /* forceInterpretHdrAsSdr= */ false, clock)
.createAssetLoader(editedMediaItem, looper, listener);
}

View File

@ -802,7 +802,12 @@ public final class TransformerEndToEndTest {
context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10));
Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
AssetLoader.Factory assetLoaderFactory =
new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory);
new ExoPlayerAssetLoader.Factory(
context,
decoderFactory,
/* forceInterpretHdrAsSdr= */ false,
clock,
mediaSourceFactory);
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1);
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)