From 5de29928cde8c89fef71ccf65e70a82680dc64dd Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 2 Feb 2023 19:16:18 -0600 Subject: [PATCH] Plumb HDR metadata for VideoToolbox --- app/streaming/video/ffmpeg-renderers/vt.mm | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/app/streaming/video/ffmpeg-renderers/vt.mm b/app/streaming/video/ffmpeg-renderers/vt.mm index d75495b7..e43de2ec 100644 --- a/app/streaming/video/ffmpeg-renderers/vt.mm +++ b/app/streaming/video/ffmpeg-renderers/vt.mm @@ -38,6 +38,8 @@ public: : m_HwContext(nullptr), m_DisplayLayer(nullptr), m_FormatDesc(nullptr), + m_ContentLightLevelInfo(nullptr), + m_MasteringDisplayColorVolume(nullptr), m_StreamView(nullptr), m_DisplayLink(nullptr), m_LastColorSpace(-1), @@ -87,6 +89,14 @@ public: CGColorSpaceRelease(m_ColorSpace); } + if (m_MasteringDisplayColorVolume != nullptr) { + CFRelease(m_MasteringDisplayColorVolume); + } + + if (m_ContentLightLevelInfo != nullptr) { + CFRelease(m_ContentLightLevelInfo); + } + for (int i = 0; i < Overlay::OverlayMax; i++) { if (m_OverlayTextFields[i] != nullptr) { [m_OverlayTextFields[i] removeFromSuperview]; @@ -183,6 +193,63 @@ public: } } + virtual void setHdrMode(bool enabled) override + { + // Free existing HDR metadata + if (m_MasteringDisplayColorVolume != nullptr) { + CFRelease(m_MasteringDisplayColorVolume); + m_MasteringDisplayColorVolume = nullptr; + } + if (m_ContentLightLevelInfo != nullptr) { + CFRelease(m_ContentLightLevelInfo); + m_ContentLightLevelInfo = nullptr; + } + + // Store new HDR metadata if available + SS_HDR_METADATA hdrMetadata; + if (enabled && LiGetHdrMetadata(&hdrMetadata)) { + if (hdrMetadata.displayPrimaries[0].x != 0 && hdrMetadata.maxDisplayLuminance != 0) { + // This data is all in big-endian + struct { + vector_ushort2 primaries[3]; + vector_ushort2 white_point; + uint32_t luminance_max; + uint32_t luminance_min; + } __attribute__((packed, aligned(4))) mdcv; + + // mdcv is in GBR order while SS_HDR_METADATA is in RGB order + mdcv.primaries[0].x = __builtin_bswap16(hdrMetadata.displayPrimaries[1].x); + mdcv.primaries[0].y = __builtin_bswap16(hdrMetadata.displayPrimaries[1].y); + mdcv.primaries[1].x = __builtin_bswap16(hdrMetadata.displayPrimaries[2].x); + mdcv.primaries[1].y = __builtin_bswap16(hdrMetadata.displayPrimaries[2].y); + mdcv.primaries[2].x = __builtin_bswap16(hdrMetadata.displayPrimaries[0].x); + mdcv.primaries[2].y = __builtin_bswap16(hdrMetadata.displayPrimaries[0].y); + + mdcv.white_point.x = __builtin_bswap16(hdrMetadata.whitePoint.x); + mdcv.white_point.y = __builtin_bswap16(hdrMetadata.whitePoint.y); + + // These luminance values are in 10000ths of a nit + mdcv.luminance_max = __builtin_bswap32((uint32_t)hdrMetadata.maxDisplayLuminance * 10000); + mdcv.luminance_min = __builtin_bswap32(hdrMetadata.minDisplayLuminance); + + m_MasteringDisplayColorVolume = CFDataCreate(nullptr, (const UInt8*)&mdcv, sizeof(mdcv)); + } + + if (hdrMetadata.maxContentLightLevel != 0 && hdrMetadata.maxFrameAverageLightLevel != 0) { + // This data is all in big-endian + struct { + uint16_t max_content_light_level; + uint16_t max_frame_average_light_level; + } __attribute__((packed, aligned(2))) cll; + + cll.max_content_light_level = __builtin_bswap16(hdrMetadata.maxContentLightLevel); + cll.max_frame_average_light_level = __builtin_bswap16(hdrMetadata.maxFrameAverageLightLevel); + + m_ContentLightLevelInfo = CFDataCreate(nullptr, (const UInt8*)&cll, sizeof(cll)); + } + } + } + // Caller frees frame after we return virtual void renderFrame(AVFrame* frame) override { @@ -238,6 +305,14 @@ public: CVBufferSetAttachment(pixBuf, kCVImageBufferCGColorSpaceKey, m_ColorSpace, kCVAttachmentMode_ShouldPropagate); } + // Attach HDR metadata if it has been provided by the host + if (m_MasteringDisplayColorVolume != nullptr) { + CVBufferSetAttachment(pixBuf, kCVImageBufferMasteringDisplayColorVolumeKey, m_MasteringDisplayColorVolume, kCVAttachmentMode_ShouldPropagate); + } + if (m_ContentLightLevelInfo != nullptr) { + CVBufferSetAttachment(pixBuf, kCVImageBufferContentLightLevelInfoKey, m_ContentLightLevelInfo, kCVAttachmentMode_ShouldPropagate); + } + // If the format has changed or doesn't exist yet, construct it with the // pixel buffer data if (!m_FormatDesc || !CMVideoFormatDescriptionMatchesImageBuffer(m_FormatDesc, pixBuf)) { @@ -512,6 +587,8 @@ private: AVBufferRef* m_HwContext; AVSampleBufferDisplayLayer* m_DisplayLayer; CMVideoFormatDescriptionRef m_FormatDesc; + CFDataRef m_ContentLightLevelInfo; + CFDataRef m_MasteringDisplayColorVolume; NSView* m_StreamView; dispatch_block_t m_OverlayUpdateBlocks[Overlay::OverlayMax]; NSTextField* m_OverlayTextFields[Overlay::OverlayMax];