Plumb HDR metadata into MediaCodec

This commit is contained in:
Cameron Gutman
2023-02-20 21:42:45 -06:00
parent f4df0714b5
commit b961636f02
6 changed files with 101 additions and 10 deletions

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);