diff --git a/app/streaming/video/ffmpeg-renderers/renderer.h b/app/streaming/video/ffmpeg-renderers/renderer.h index 6951ede8..b1397bf2 100644 --- a/app/streaming/video/ffmpeg-renderers/renderer.h +++ b/app/streaming/video/ffmpeg-renderers/renderer.h @@ -2,11 +2,14 @@ #include "SDL_compat.h" +#include + #include "streaming/video/decoder.h" #include "streaming/video/overlaymanager.h" extern "C" { #include +#include #ifdef HAVE_DRM #include @@ -325,6 +328,156 @@ public: } } + AVPixelFormat getFrameSwPixelFormat(const AVFrame* frame) { + // For hwaccel formats, we want to get the real underlying format + if (frame->hw_frames_ctx) { + return ((AVHWFramesContext*)frame->hw_frames_ctx->data)->sw_format; + } + else { + return (AVPixelFormat)frame->format; + } + } + + int getFrameBitsPerChannel(const AVFrame* frame) { + const AVPixFmtDescriptor* formatDesc = av_pix_fmt_desc_get(getFrameSwPixelFormat(frame)); + if (!formatDesc) { + // This shouldn't be possible but handle it anyway + SDL_assert(formatDesc); + return 8; + } + + // This assumes plane 0 is exclusively the Y component + return formatDesc->comp[0].depth; + } + + void getFramePremultipliedCscConstants(const AVFrame* frame, std::array &cscMatrix, std::array &offsets) { + static const std::array k_CscMatrix_Bt601 = { + 1.0f, 1.0f, 1.0f, + 0.0f, -0.3441f, 1.7720f, + 1.4020f, -0.7141f, 0.0f, + }; + static const std::array k_CscMatrix_Bt709 = { + 1.0f, 1.0f, 1.0f, + 0.0f, -0.1873f, 1.8556f, + 1.5748f, -0.4681f, 0.0f, + }; + static const std::array k_CscMatrix_Bt2020 = { + 1.0f, 1.0f, 1.0f, + 0.0f, -0.1646f, 1.8814f, + 1.4746f, -0.5714f, 0.0f, + }; + + bool fullRange = isFrameFullRange(frame); + int bitsPerChannel = getFrameBitsPerChannel(frame); + int channelRange = (1 << bitsPerChannel); + double yMin = (fullRange ? 0 : (16 << (bitsPerChannel - 8))); + double yMax = (fullRange ? (channelRange - 1) : (235 << (bitsPerChannel - 8))); + double yScale = (channelRange - 1) / (yMax - yMin); + double uvMin = (fullRange ? 0 : (16 << (bitsPerChannel - 8))); + double uvMax = (fullRange ? (channelRange - 1) : (240 << (bitsPerChannel - 8))); + double uvScale = (channelRange - 1) / (uvMax - uvMin); + + // Calculate YUV offsets + offsets[0] = yMin / (double)(channelRange - 1); + offsets[1] = (channelRange / 2) / (double)(channelRange - 1); + offsets[2] = (channelRange / 2) / (double)(channelRange - 1); + + // Start with the standard full range color matrix + switch (getFrameColorspace(frame)) { + default: + case COLORSPACE_REC_601: + cscMatrix = k_CscMatrix_Bt601; + break; + case COLORSPACE_REC_709: + cscMatrix = k_CscMatrix_Bt709; + break; + case COLORSPACE_REC_2020: + cscMatrix = k_CscMatrix_Bt2020; + break; + } + + // Scale the color matrix according to the color range + for (int i = 0; i < 3; i++) { + cscMatrix[i] *= yScale; + } + for (int i = 3; i < 9; i++) { + cscMatrix[i] *= uvScale; + } + } + + void getFrameChromaCositingOffsets(const AVFrame* frame, std::array &chromaOffsets) { + const AVPixFmtDescriptor* formatDesc = av_pix_fmt_desc_get(getFrameSwPixelFormat(frame)); + if (!formatDesc) { + SDL_assert(formatDesc); + chromaOffsets.fill(0); + return; + } + + SDL_assert(formatDesc->log2_chroma_w <= 1); + SDL_assert(formatDesc->log2_chroma_h <= 1); + + switch (frame->chroma_location) { + default: + case AVCHROMA_LOC_LEFT: + chromaOffsets[0] = 0.5; + chromaOffsets[1] = 0; + break; + case AVCHROMA_LOC_CENTER: + chromaOffsets[0] = 0; + chromaOffsets[1] = 0; + break; + case AVCHROMA_LOC_TOPLEFT: + chromaOffsets[0] = 0.5; + chromaOffsets[1] = 0.5; + break; + case AVCHROMA_LOC_TOP: + chromaOffsets[0] = 0; + chromaOffsets[1] = 0.5; + break; + case AVCHROMA_LOC_BOTTOMLEFT: + chromaOffsets[0] = 0.5; + chromaOffsets[1] = -0.5; + break; + case AVCHROMA_LOC_BOTTOM: + chromaOffsets[0] = 0; + chromaOffsets[1] = -0.5; + break; + } + + // Force the offsets to 0 if chroma is not subsampled in that dimension + if (formatDesc->log2_chroma_w == 0) { + chromaOffsets[0] = 0; + } + if (formatDesc->log2_chroma_h == 0) { + chromaOffsets[1] = 0; + } + } + + // Returns if the frame format has changed since the last call to this function + bool hasFrameFormatChanged(const AVFrame* frame) { + AVPixelFormat format = getFrameSwPixelFormat(frame); + if (frame->width == m_LastFrameWidth && + frame->height == m_LastFrameHeight && + format == m_LastFramePixelFormat && + frame->color_range == m_LastColorRange && + frame->color_primaries == m_LastColorPrimaries && + frame->color_trc == m_LastColorTrc && + frame->colorspace == m_LastColorSpace && + frame->chroma_location == m_LastChromaLocation) { + return false; + } + + m_LastFrameWidth = frame->width; + m_LastFrameHeight = frame->height; + m_LastFramePixelFormat = format; + m_LastColorRange = frame->color_range; + m_LastColorPrimaries = frame->color_primaries; + m_LastColorTrc = frame->color_trc; + m_LastColorSpace = frame->colorspace; + m_LastChromaLocation = frame->chroma_location; + return true; + } + // IOverlayRenderer virtual void notifyOverlayUpdated(Overlay::OverlayType) override { // Nothing @@ -373,4 +526,14 @@ protected: private: RendererType m_Type; + + // Properties watched by hasFrameFormatChanged() + int m_LastFrameWidth = 0; + int m_LastFrameHeight = 0; + AVPixelFormat m_LastFramePixelFormat = AV_PIX_FMT_NONE; + AVColorRange m_LastColorRange = AVCOL_RANGE_UNSPECIFIED; + AVColorPrimaries m_LastColorPrimaries = AVCOL_PRI_UNSPECIFIED; + AVColorTransferCharacteristic m_LastColorTrc = AVCOL_TRC_UNSPECIFIED; + AVColorSpace m_LastColorSpace = AVCOL_SPC_UNSPECIFIED; + AVChromaLocation m_LastChromaLocation = AVCHROMA_LOC_UNSPECIFIED; };