Skip to content

Commit 6ddc818

Browse files
kggibsoncopybara-github
authored andcommitted
Fix MediaCodec tone mapping in CompositionPlayer
Previously an incorrect ColorInfo was being set on the VideoGraph when the input was tone mapped with MediaCodec. PiperOrigin-RevId: 765093463
1 parent 002b84a commit 6ddc818

File tree

3 files changed

+109
-6
lines changed

3 files changed

+109
-6
lines changed

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapper.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ public PlaybackVideoGraphWrapper build() {
253253
private @MonotonicNonNull VideoGraph videoGraph;
254254
private @MonotonicNonNull VideoFrameMetadataListener videoFrameMetadataListener;
255255
private boolean requestOpenGlToneMapping;
256+
private boolean isInputSdrToneMapped;
256257
private long outputStreamStartPositionUs;
257258
private @VideoSink.FirstFrameReleaseInstruction int outputStreamFirstFrameReleaseInstruction;
258259
@Nullable private Pair<Surface, Size> currentSurfaceAndSize;
@@ -390,6 +391,11 @@ public void setRequestOpenGlToneMapping(boolean requestOpenGlToneMapping) {
390391
this.requestOpenGlToneMapping = requestOpenGlToneMapping;
391392
}
392393

394+
/** Sets whether the input should be treated as if it has been tone mapped to SDR. */
395+
public void setIsInputSdrToneMapped(boolean isInputSdrToneMapped) {
396+
this.isInputSdrToneMapped = isInputSdrToneMapped;
397+
}
398+
393399
public void release() {
394400
if (state == STATE_RELEASED) {
395401
return;
@@ -642,8 +648,8 @@ private boolean shouldRenderToInputVideoSink() {
642648
&& totalVideoInputCount == registeredVideoInputCount;
643649
}
644650

645-
private static ColorInfo getAdjustedInputColorInfo(@Nullable ColorInfo inputColorInfo) {
646-
if (inputColorInfo == null || !inputColorInfo.isDataSpaceValid()) {
651+
private ColorInfo getAdjustedInputColorInfo(@Nullable ColorInfo inputColorInfo) {
652+
if (inputColorInfo == null || !inputColorInfo.isDataSpaceValid() || isInputSdrToneMapped) {
647653
return ColorInfo.SDR_BT709_LIMITED;
648654
}
649655

libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapperTest.java

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
2020
import static com.google.common.truth.Truth.assertThat;
2121
import static org.junit.Assert.assertThrows;
22-
import static org.mockito.ArgumentMatchers.any;
2322
import static org.mockito.ArgumentMatchers.anyInt;
2423
import static org.mockito.ArgumentMatchers.anyLong;
2524
import static org.mockito.ArgumentMatchers.eq;
@@ -28,6 +27,7 @@
2827
import static org.mockito.Mockito.when;
2928

3029
import android.content.Context;
30+
import androidx.media3.common.C;
3131
import androidx.media3.common.ColorInfo;
3232
import androidx.media3.common.DebugViewProvider;
3333
import androidx.media3.common.Effect;
@@ -100,11 +100,100 @@ public void onInputStreamChanged_setsVideoSinkVideoEffects() throws VideoSink.Vi
100100
startPositionUs,
101101
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
102102
ImmutableList.of());
103-
testVideoGraphFactory.verifyRegisteredEffectsMatches(/* invocationTimes= */ 3);
103+
testVideoGraphFactory.verifyRegisterInputStream(/* invocationTimes= */ 3);
104104
assertThat(testVideoGraphFactory.getCapturedEffects())
105105
.isEqualTo(ImmutableList.of(firstEffects, secondEffects, ImmutableList.of()));
106106
}
107107

108+
@Test
109+
public void onInputStreamChanged_withNoToneMapping_initializesGraphWithInputColorInfo()
110+
throws VideoSink.VideoSinkException {
111+
TestVideoGraphFactory testVideoGraphFactory = new TestVideoGraphFactory();
112+
PlaybackVideoGraphWrapper playbackVideoGraphWrapper =
113+
createPlaybackVideoGraphWrapper(testVideoGraphFactory);
114+
ColorInfo hlgColorInfo =
115+
new ColorInfo.Builder()
116+
.setColorRange(C.COLOR_RANGE_FULL)
117+
.setColorSpace(C.COLOR_SPACE_BT2020)
118+
.setColorTransfer(C.COLOR_TRANSFER_HLG)
119+
.build();
120+
Format inputFormat = new Format.Builder().setColorInfo(hlgColorInfo).build();
121+
long startPositionUs = 0;
122+
VideoSink sink = playbackVideoGraphWrapper.getSink(/* inputIndex= */ 0);
123+
124+
sink.initialize(inputFormat);
125+
sink.onInputStreamChanged(
126+
VideoSink.INPUT_TYPE_SURFACE,
127+
inputFormat,
128+
startPositionUs,
129+
RELEASE_FIRST_FRAME_IMMEDIATELY,
130+
/* videoEffects= */ ImmutableList.of());
131+
132+
testVideoGraphFactory.verifyRegisterInputStream(/* invocationTimes= */ 1);
133+
assertThat(testVideoGraphFactory.getCapturedFormats()).containsExactly(inputFormat);
134+
}
135+
136+
@Test
137+
public void onInputStreamChanged_withInputSdrToneMapped_initializesGraphWithBt709Input()
138+
throws VideoSink.VideoSinkException {
139+
TestVideoGraphFactory testVideoGraphFactory = new TestVideoGraphFactory();
140+
PlaybackVideoGraphWrapper playbackVideoGraphWrapper =
141+
createPlaybackVideoGraphWrapper(testVideoGraphFactory);
142+
ColorInfo hlgColorInfo =
143+
new ColorInfo.Builder()
144+
.setColorRange(C.COLOR_RANGE_FULL)
145+
.setColorSpace(C.COLOR_SPACE_BT2020)
146+
.setColorTransfer(C.COLOR_TRANSFER_HLG)
147+
.build();
148+
Format inputFormat = new Format.Builder().setColorInfo(hlgColorInfo).build();
149+
long startPositionUs = 0;
150+
VideoSink sink = playbackVideoGraphWrapper.getSink(/* inputIndex= */ 0);
151+
152+
playbackVideoGraphWrapper.setIsInputSdrToneMapped(true);
153+
sink.initialize(inputFormat);
154+
sink.onInputStreamChanged(
155+
VideoSink.INPUT_TYPE_SURFACE,
156+
inputFormat,
157+
startPositionUs,
158+
RELEASE_FIRST_FRAME_IMMEDIATELY,
159+
/* videoEffects= */ ImmutableList.of());
160+
161+
testVideoGraphFactory.verifyRegisterInputStream(/* invocationTimes= */ 1);
162+
Format bt709ColorInfoInputFormat =
163+
inputFormat.buildUpon().setColorInfo(ColorInfo.SDR_BT709_LIMITED).build();
164+
assertThat(testVideoGraphFactory.getCapturedFormats())
165+
.containsExactly(bt709ColorInfoInputFormat);
166+
}
167+
168+
@Test
169+
public void onInputStreamChanged_withOpenGlToneMapping_initializesGraphWithInputColorInfo()
170+
throws VideoSink.VideoSinkException {
171+
TestVideoGraphFactory testVideoGraphFactory = new TestVideoGraphFactory();
172+
PlaybackVideoGraphWrapper playbackVideoGraphWrapper =
173+
createPlaybackVideoGraphWrapper(testVideoGraphFactory);
174+
ColorInfo hlgColorInfo =
175+
new ColorInfo.Builder()
176+
.setColorRange(C.COLOR_RANGE_FULL)
177+
.setColorSpace(C.COLOR_SPACE_BT2020)
178+
.setColorTransfer(C.COLOR_TRANSFER_HLG)
179+
.build();
180+
Format inputFormat = new Format.Builder().setColorInfo(hlgColorInfo).build();
181+
long startPositionUs = 0;
182+
VideoSink sink = playbackVideoGraphWrapper.getSink(/* inputIndex= */ 0);
183+
184+
playbackVideoGraphWrapper.setRequestOpenGlToneMapping(true);
185+
sink.initialize(inputFormat);
186+
sink.onInputStreamChanged(
187+
VideoSink.INPUT_TYPE_SURFACE,
188+
inputFormat,
189+
startPositionUs,
190+
RELEASE_FIRST_FRAME_IMMEDIATELY,
191+
/* videoEffects= */ ImmutableList.of());
192+
193+
testVideoGraphFactory.verifyRegisterInputStream(/* invocationTimes= */ 1);
194+
assertThat(testVideoGraphFactory.getCapturedFormats()).containsExactly(inputFormat);
195+
}
196+
108197
private static PlaybackVideoGraphWrapper createPlaybackVideoGraphWrapper(
109198
VideoGraph.Factory videoGraphFactory) {
110199
Context context = ApplicationProvider.getApplicationContext();
@@ -150,6 +239,8 @@ private static class TestVideoGraphFactory implements VideoGraph.Factory {
150239
@SuppressWarnings("unchecked")
151240
private final ArgumentCaptor<List<Effect>> effectsCaptor = ArgumentCaptor.forClass(List.class);
152241

242+
private final ArgumentCaptor<Format> formatCaptor = ArgumentCaptor.forClass(Format.class);
243+
153244
@Override
154245
public VideoGraph create(
155246
Context context,
@@ -171,18 +262,22 @@ public boolean supportsMultipleInputs() {
171262
return false;
172263
}
173264

174-
public void verifyRegisteredEffectsMatches(int invocationTimes) {
265+
public void verifyRegisterInputStream(int invocationTimes) {
175266
verify(videoGraph, times(invocationTimes))
176267
.registerInputStream(
177268
/* inputIndex= */ anyInt(),
178269
/* inputType= */ eq(VideoSink.INPUT_TYPE_SURFACE),
179-
/* format= */ any(),
270+
formatCaptor.capture(),
180271
effectsCaptor.capture(),
181272
/* offsetToAddUs= */ anyLong());
182273
}
183274

184275
public List<List<Effect>> getCapturedEffects() {
185276
return effectsCaptor.getAllValues();
186277
}
278+
279+
public List<Format> getCapturedFormats() {
280+
return formatCaptor.getAllValues();
281+
}
187282
}
188283
}

libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayerInternal.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ private void setCompositionInternal(Composition composition) {
212212
playbackVideoGraphWrapper.setCompositorSettings(composition.videoCompositorSettings);
213213
playbackVideoGraphWrapper.setRequestOpenGlToneMapping(
214214
composition.hdrMode == Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL);
215+
playbackVideoGraphWrapper.setIsInputSdrToneMapped(
216+
composition.hdrMode == Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC);
215217
}
216218

217219
private void releaseInternal(ConditionVariable conditionVariable) {

0 commit comments

Comments
 (0)