diff --git a/src/ControlStream.c b/src/ControlStream.c index 60238ed..90ff624 100644 --- a/src/ControlStream.c +++ b/src/ControlStream.c @@ -50,6 +50,7 @@ static bool stopping; static bool disconnectPending; static bool encryptedControlStream; static bool hdrEnabled; +static SS_HDR_METADATA hdrMetadata; static int intervalGoodFrameCount; static int intervalTotalFrameCount; @@ -275,6 +276,7 @@ int initializeControlStream(void) { encryptionCtx = PltCreateCryptoContext(); decryptionCtx = PltCreateCryptoContext(); hdrEnabled = false; + memset(&hdrMetadata, 0, sizeof(hdrMetadata)); return 0; } @@ -813,9 +815,24 @@ static void controlReceiveThreadFunc(void* context) { BbInitializeWrappedBuffer(&bb, (char*)ctlHdr, sizeof(*ctlHdr), packetLength - sizeof(*ctlHdr), BYTE_ORDER_LITTLE); - // FIXME: There are 7 additional bytes that appear to always be all zeros. What do they mean? - // Is there some way that GFE tells us the HDR mastering metadata (NV_HDR_COLOR_DATA) set by the game? BbGet8(&bb, &enableByte); + if (IS_SUNSHINE()) { + // Zero the metadata buffer to properly handle older servers if we have to add new fields + memset(&hdrMetadata, 0, sizeof(hdrMetadata)); + + // Sunshine sends HDR metadata in this message too + for (int i = 0; i < 3; i++) { + BbGet16(&bb, &hdrMetadata.displayPrimaries[i].x); + BbGet16(&bb, &hdrMetadata.displayPrimaries[i].y); + } + BbGet16(&bb, &hdrMetadata.whitePoint.x); + BbGet16(&bb, &hdrMetadata.whitePoint.y); + BbGet16(&bb, &hdrMetadata.maxDisplayLuminance); + BbGet16(&bb, &hdrMetadata.minDisplayLuminance); + BbGet16(&bb, &hdrMetadata.maxContentLightLevel); + BbGet16(&bb, &hdrMetadata.maxFrameAverageLightLevel); + BbGet16(&bb, &hdrMetadata.maxFullFrameLuminance); + } hdrEnabled = (enableByte != 0); ListenerCallbacks.setHdrMode(hdrEnabled); @@ -1447,3 +1464,12 @@ int startControlStream(void) { bool LiGetCurrentHostDisplayHdrMode(void) { return hdrEnabled; } + +bool LiGetHdrMetadata(PSS_HDR_METADATA metadata) { + if (!IS_SUNSHINE() || !hdrEnabled) { + return false; + } + + *metadata = hdrMetadata; + return true; +} diff --git a/src/Limelight.h b/src/Limelight.h index 06d474d..0a937ff 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -705,6 +705,34 @@ void LiCompleteVideoFrame(VIDEO_FRAME_HANDLE handle, int drStatus); // See ConnListenerSetHdrMode() for more details. bool LiGetCurrentHostDisplayHdrMode(void); +typedef struct _SS_HDR_METADATA { + // RGB order + struct { + uint16_t x; // Normalized to 50,000 + uint16_t y; // Normalized to 50,000 + } displayPrimaries[3]; + + struct { + uint16_t x; // Normalized to 50,000 + uint16_t y; // Normalized to 50,000 + } whitePoint; + + uint16_t maxDisplayLuminance; // Nits + uint16_t minDisplayLuminance; // 1/10000th of a nit + + // These are content-specific values which may not be available for all hosts. + uint16_t maxContentLightLevel; // Nits + uint16_t maxFrameAverageLightLevel; // Nits + + // These are display-specific values which may not be available for all hosts. + uint16_t maxFullFrameLuminance; // Nits +} SS_HDR_METADATA, *PSS_HDR_METADATA; + +// This function populates the provided mastering metadata struct with the HDR metadata +// from the host PC's monitor and content (if available). It is only valid to call this +// function when HDR mode is active on the host. This is a Sunshine protocol extension. +bool LiGetHdrMetadata(PSS_HDR_METADATA metadata); + // This function requests an IDR frame from the host. Typically this is done using DR_NEED_IDR, but clients // processing frames asynchronously may need to reset their decoder state even after returning DR_OK for // the prior frame. Rather than wait for a new frame and return DR_NEED_IDR for that one, they can just