mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-02-16 02:20:55 +00:00
Plumb HDR metadata into MediaCodec
This commit is contained in:
@@ -2073,9 +2073,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHdrMode(boolean enabled) {
|
||||
public void setHdrMode(boolean enabled, byte[] hdrMetadata) {
|
||||
LimeLog.info("Display HDR mode: " + (enabled ? "enabled" : "disabled"));
|
||||
decoderRenderer.setHdrMode(enabled);
|
||||
decoderRenderer.setHdrMode(enabled, hdrMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.limelight.binding.video;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -49,6 +51,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
private byte[] ppsBuffer;
|
||||
private boolean submittedCsd;
|
||||
private boolean submitCsdNextCall;
|
||||
private byte[] currentHdrMetadata;
|
||||
|
||||
private int nextInputBufferIndex = -1;
|
||||
private ByteBuffer nextInputBuffer;
|
||||
@@ -379,10 +382,64 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, initialHeight);
|
||||
}
|
||||
|
||||
// Android 7.0 adds color options to the MediaFormat
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_RANGE,
|
||||
getPreferredColorRange() == MoonBridge.COLOR_RANGE_FULL ?
|
||||
MediaFormat.COLOR_RANGE_FULL : MediaFormat.COLOR_RANGE_LIMITED);
|
||||
|
||||
// If the stream is HDR-capable, the decoder will detect transitions in color standards
|
||||
// rather than us hardcoding them into the MediaFormat.
|
||||
if (getActiveVideoFormat() != MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
|
||||
// Set color format keys when not in HDR mode, since we know they won't change
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
|
||||
switch (getPreferredColorSpace()) {
|
||||
case MoonBridge.COLORSPACE_REC_601:
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_NTSC);
|
||||
break;
|
||||
case MoonBridge.COLORSPACE_REC_709:
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT709);
|
||||
break;
|
||||
case MoonBridge.COLORSPACE_REC_2020:
|
||||
videoFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT2020);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return videoFormat;
|
||||
}
|
||||
|
||||
private void configureAndStartDecoder(MediaFormat format) {
|
||||
// Set HDR metadata if present
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (currentHdrMetadata != null) {
|
||||
ByteBuffer hdrStaticInfo = ByteBuffer.allocate(25).order(ByteOrder.LITTLE_ENDIAN);
|
||||
ByteBuffer hdrMetadata = ByteBuffer.wrap(currentHdrMetadata).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
// Create a HDMI Dynamic Range and Mastering InfoFrame as defined by CTA-861.3
|
||||
hdrStaticInfo.put((byte) 0); // Metadata type
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // RX
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // RY
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // GX
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // GY
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // BX
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // BY
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // White X
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // White Y
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // Max mastering luminance
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // Min mastering luminance
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // Max content luminance
|
||||
hdrStaticInfo.putShort(hdrMetadata.getShort()); // Max frame average luminance
|
||||
|
||||
hdrStaticInfo.rewind();
|
||||
format.setByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO, hdrStaticInfo);
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
format.removeKey(MediaFormat.KEY_HDR_STATIC_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Configuring with format: "+format);
|
||||
|
||||
videoDecoder.configure(format, renderTarget.getSurface(), null, 0);
|
||||
@@ -1140,8 +1197,33 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHdrMode(boolean enabled) {
|
||||
// TODO: Set HDR metadata?
|
||||
public void setHdrMode(boolean enabled, byte[] hdrMetadata) {
|
||||
// HDR metadata is only supported in Android 7.0 and later, so don't bother
|
||||
// restarting the codec on anything earlier than that.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (currentHdrMetadata != null && (!enabled || hdrMetadata == null)) {
|
||||
currentHdrMetadata = null;
|
||||
}
|
||||
else if (enabled && hdrMetadata != null && !Arrays.equals(currentHdrMetadata, hdrMetadata)) {
|
||||
currentHdrMetadata = hdrMetadata;
|
||||
}
|
||||
else {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// If we reach this point, we need to restart the MediaCodec instance to
|
||||
// pick up the HDR metadata change. This will happen on the next input
|
||||
// or output buffer.
|
||||
|
||||
// HACK: Reset codec recovery attempt counter, since this is an expected "recovery"
|
||||
codecRecoveryAttempts = 0;
|
||||
|
||||
// Promote None/Flush to Restart and leave Reset alone
|
||||
if (!codecRecoveryType.compareAndSet(CR_RECOVERY_TYPE_NONE, CR_RECOVERY_TYPE_RESTART)) {
|
||||
codecRecoveryType.compareAndSet(CR_RECOVERY_TYPE_FLUSH, CR_RECOVERY_TYPE_RESTART);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean queueNextInputBuffer(long timestampUs, int codecFlags) {
|
||||
|
||||
@@ -14,5 +14,5 @@ public interface NvConnectionListener {
|
||||
|
||||
void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor);
|
||||
|
||||
void setHdrMode(boolean enabled);
|
||||
void setHdrMode(boolean enabled, byte[] hdrMetadata);
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@ public abstract class VideoDecoderRenderer {
|
||||
|
||||
public abstract int getCapabilities();
|
||||
|
||||
public abstract void setHdrMode(boolean enabled);
|
||||
public abstract void setHdrMode(boolean enabled, byte[] hdrMetadata);
|
||||
}
|
||||
|
||||
@@ -253,9 +253,9 @@ public class MoonBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public static void bridgeClSetHdrMode(boolean enabled) {
|
||||
public static void bridgeClSetHdrMode(boolean enabled, byte[] hdrMetadata) {
|
||||
if (connectionListener != null) {
|
||||
connectionListener.setHdrMode(enabled);
|
||||
connectionListener.setHdrMode(enabled, hdrMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeClConnectionTerminatedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionTerminated", "(I)V");
|
||||
BridgeClRumbleMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumble", "(SSS)V");
|
||||
BridgeClConnectionStatusUpdateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStatusUpdate", "(I)V");
|
||||
BridgeClSetHdrModeMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetHdrMode", "(Z)V");
|
||||
BridgeClSetHdrModeMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetHdrMode", "(Z[B)V");
|
||||
}
|
||||
|
||||
int BridgeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
@@ -331,7 +331,16 @@ void BridgeClConnectionStatusUpdate(int connectionStatus) {
|
||||
void BridgeClSetHdrMode(bool enabled) {
|
||||
JNIEnv* env = GetThreadEnv();
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetHdrModeMethod, enabled);
|
||||
jbyteArray hdrMetadataByteArray = NULL;
|
||||
SS_HDR_METADATA hdrMetadata;
|
||||
|
||||
// Check if HDR metadata was provided
|
||||
if (enabled && LiGetHdrMetadata(&hdrMetadata)) {
|
||||
hdrMetadataByteArray = (*env)->NewByteArray(env, sizeof(SS_HDR_METADATA));
|
||||
(*env)->SetByteArrayRegion(env, hdrMetadataByteArray, 0, sizeof(SS_HDR_METADATA), (jbyte*)&hdrMetadata);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetHdrModeMethod, enabled, hdrMetadataByteArray);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
|
||||
Reference in New Issue
Block a user