mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Frame Extractor HDR: tone map to SDR
Support extracting frames from HDR input by tone mapping to SDR (BT.709). ExperimentalFrameExtractor must be public because HDR tests live in a different package. PiperOrigin-RevId: 699994112
This commit is contained in:
parent
0d8f1d5ab9
commit
bb20eb4975
@ -530,6 +530,24 @@ public final class AndroidTestUtil {
|
||||
.build())
|
||||
.build();
|
||||
|
||||
public static final AssetInfo MP4_ASSET_COLOR_TEST_1080P_HLG10 =
|
||||
new AssetInfo.Builder("asset:///media/mp4/hlg10-color-test.mp4")
|
||||
.setVideoFormat(
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(VIDEO_H265)
|
||||
.setWidth(1920)
|
||||
.setHeight(1080)
|
||||
.setFrameRate(30.000f)
|
||||
.setColorInfo(
|
||||
new ColorInfo.Builder()
|
||||
.setColorSpace(C.COLOR_SPACE_BT2020)
|
||||
.setColorRange(C.COLOR_RANGE_LIMITED)
|
||||
.setColorTransfer(C.COLOR_TRANSFER_HLG)
|
||||
.build())
|
||||
.setCodecs("hvc1.2.4.L153")
|
||||
.build())
|
||||
.build();
|
||||
|
||||
public static final AssetInfo MP4_ASSET_720P_4_SECOND_HDR10 =
|
||||
new AssetInfo.Builder("asset:///media/mp4/hdr10-720p.mp4")
|
||||
.setVideoFormat(
|
||||
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.transformer.mh;
|
||||
|
||||
import static androidx.media3.test.utils.BitmapPixelTestUtil.maybeSaveTestBitmap;
|
||||
import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap;
|
||||
import static androidx.media3.test.utils.TestUtil.assertBitmapsAreSimilar;
|
||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_COLOR_TEST_1080P_HLG10;
|
||||
import static androidx.media3.transformer.mh.HdrCapabilitiesUtil.assumeDeviceSupportsOpenGlToneMapping;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.transformer.ExperimentalFrameExtractor;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** End-to-end HDR instrumentation test for {@link ExperimentalFrameExtractor}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class FrameExtractorHdrTest {
|
||||
// This file is generated on a Pixel 7, because the emulator isn't able to decode HLG to generate
|
||||
// this file.
|
||||
private static final String TONE_MAP_HLG_TO_SDR_PNG_ASSET_PATH =
|
||||
"test-generated-goldens/sample_mp4_first_frame/electrical_colors/tone_map_hlg_to_sdr.png";
|
||||
private static final long TIMEOUT_SECONDS = 10;
|
||||
private static final float PSNR_THRESHOLD = 25f;
|
||||
|
||||
@Rule public final TestName testName = new TestName();
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private String testId;
|
||||
private @MonotonicNonNull ExperimentalFrameExtractor frameExtractor;
|
||||
|
||||
@Before
|
||||
public void setUpTestId() {
|
||||
testId = testName.getMethodName();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (frameExtractor != null) {
|
||||
frameExtractor.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractFrame_oneFrameHlg_returnsToneMappedFrame() throws Exception {
|
||||
assumeDeviceSupportsOpenGlToneMapping(testId, MP4_ASSET_COLOR_TEST_1080P_HLG10.videoFormat);
|
||||
frameExtractor =
|
||||
new ExperimentalFrameExtractor(
|
||||
context,
|
||||
new ExperimentalFrameExtractor.Configuration.Builder().build(),
|
||||
MediaItem.fromUri(MP4_ASSET_COLOR_TEST_1080P_HLG10.uri),
|
||||
/* effects= */ ImmutableList.of());
|
||||
|
||||
ListenableFuture<ExperimentalFrameExtractor.Frame> frameFuture =
|
||||
frameExtractor.getFrame(/* positionMs= */ 0);
|
||||
ExperimentalFrameExtractor.Frame frame = frameFuture.get(TIMEOUT_SECONDS, SECONDS);
|
||||
Bitmap actualBitmap = frame.bitmap;
|
||||
Bitmap expectedBitmap = readBitmap(TONE_MAP_HLG_TO_SDR_PNG_ASSET_PATH);
|
||||
maybeSaveTestBitmap(testId, /* bitmapLabel= */ "actual", actualBitmap, /* path= */ null);
|
||||
|
||||
assertThat(frame.presentationTimeMs).isEqualTo(0);
|
||||
assertBitmapsAreSimilar(expectedBitmap, actualBitmap, PSNR_THRESHOLD);
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
|
||||
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.PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK;
|
||||
import static androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
@ -30,6 +32,7 @@ import android.media.MediaCodec;
|
||||
import android.opengl.GLES20;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.Effect;
|
||||
@ -42,6 +45,7 @@ import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
import androidx.media3.common.util.GlUtil;
|
||||
import androidx.media3.common.util.NullableType;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.effect.GlEffect;
|
||||
import androidx.media3.effect.GlShaderProgram;
|
||||
@ -79,7 +83,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
*
|
||||
* <p>Frame extractor instances must be accessed from a single application thread.
|
||||
*/
|
||||
/* package */ final class ExperimentalFrameExtractor implements AnalyticsListener {
|
||||
@UnstableApi
|
||||
public final class ExperimentalFrameExtractor implements AnalyticsListener {
|
||||
|
||||
/** Configuration for the frame extractor. */
|
||||
public static final class Configuration {
|
||||
@ -428,6 +433,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
setEffectsWithRotation();
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onReadyToInitializeCodec(Format format) throws ExoPlaybackException {
|
||||
if (isTransferHdr(format.colorInfo)) {
|
||||
// Setting the VideoSink format to SDR_BT709_LIMITED tone maps to SDR.
|
||||
format = format.buildUpon().setColorInfo(SDR_BT709_LIMITED).build();
|
||||
}
|
||||
super.onReadyToInitializeCodec(format);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||
|
Loading…
x
Reference in New Issue
Block a user