Files
UnrealEngine/Engine/Plugins/Media/ElectraUtil/Source/ElectraSamples/Private/ElectraTextureSampleBase.cpp
2025-05-18 13:04:45 +08:00

355 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Misc/Build.h"
#if !UE_SERVER
#include "HAL/IConsoleManager.h"
#include "HAL/Platform.h"
#include "ElectraTextureSample.h"
#include "ElectraTextureSampleUtils.h"
#include "ElectraSamplesModule.h"
// -------------------------------------------------------------------------------------------------------------------------------------------------------
namespace
{
static TOptional<FTimecode> CreateTimecodeFromMPEGDefinition(TOptional<FFrameRate>& OutFramerate, const IVideoDecoderTimecode::FMPEGDefinition* InMPEGTimecode)
{
if (InMPEGTimecode->timing_info_present_flag)
{
const FTimespan ts(Electra::FTimeValue(InMPEGTimecode->clockTimestamp, InMPEGTimecode->time_scale).GetAsTimespan());
OutFramerate = FFrameRate(InMPEGTimecode->time_scale, InMPEGTimecode->num_units_in_tick);
return FTimecode::FromTimespan(ts, OutFramerate.GetValue(), InMPEGTimecode->ct_type > 1, false);
}
return TOptional<FTimecode>();
}
}
static TAutoConsoleVariable<float> CVarElectraHdrWhiteLevel(
TEXT("Electra.HDR.WhiteLevel"),
MediaTextureSample::kLinearToNitsScale_BT2408,
TEXT("White level as a linear to nits scale factor.\n")
TEXT("(default: 203.0)"),
ECVF_Default);
static TAutoConsoleVariable<int32> CVarElectraHdrToneMapMethod(
TEXT("Electra.HDR.ToneMapMethod"),
static_cast<int32>(MediaShaders::EToneMapMethod::Hable),
TEXT("Tone mapping method applied on source HDR media:\n")
TEXT(" 0: None\n")
TEXT(" 1: Hable (default)\n")
TEXT(" 2: SimpleReinhard\n"),
ECVF_Default);
// -------------------------------------------------------------------------------------------------------------------------------------------------------
void IElectraTextureSampleBase::Initialize(FVideoDecoderOutput* InVideoDecoderOutput)
{
VideoDecoderOutput = StaticCastSharedPtr<FVideoDecoderOutput, IDecoderOutputPoolable, ESPMode::ThreadSafe>(InVideoDecoderOutput->AsShared());
Colorimetry = VideoDecoderOutput->GetColorimetry();
HDRInfo = VideoDecoderOutput->GetHDRInformation();
TSharedPtr<const IVideoDecoderTimecode, ESPMode::ThreadSafe> TimecodePtr = VideoDecoderOutput->GetTimecode();
if (TimecodePtr.IsValid())
{
// Store this in case this is needed again.
DecoderTimecode = TimecodePtr;
Timecode = CreateTimecodeFromMPEGDefinition(Framerate, TimecodePtr->GetMPEGDefinition());
}
else if (VideoDecoderOutput->GetDict().HaveKey(IDecoderOutputOptionNames::TMCDTimecode))
{
Timecode = VideoDecoderOutput->GetDict().GetValue(IDecoderOutputOptionNames::TMCDTimecode).GetTimecode();
Framerate = VideoDecoderOutput->GetDict().GetValue(IDecoderOutputOptionNames::TMCDFramerate).SafeGetFramerate();
}
// Get various basic MP4-style colorimetry values (we default to video range Rec709 SDR)
bool bFullRange = false;
uint8 ColorPrimaries = ElectraColorimetryUtils::DefaultMPEGColorPrimaries;
uint8 TransferCharacteristics = ElectraColorimetryUtils::DefaultMPEGMatrixCoefficients;
uint8 MatrixCoefficients = ElectraColorimetryUtils::DefaultMPEGTransferCharacteristics;
if (auto PinnedColorimetry = Colorimetry.Pin())
{
bFullRange = (PinnedColorimetry->GetMPEGDefinition()->VideoFullRangeFlag != 0);
ColorPrimaries = PinnedColorimetry->GetMPEGDefinition()->ColourPrimaries;
TransferCharacteristics = PinnedColorimetry->GetMPEGDefinition()->TransferCharacteristics;
MatrixCoefficients = PinnedColorimetry->GetMPEGDefinition()->MatrixCoefficients;
}
// Compute the bits per component in the data we get passed in
EPixelFormat PixFmt = VideoDecoderOutput->GetFormat();
uint8 NumBits = 8;
if (!IsDXTCBlockCompressedTextureFormat(PixFmt))
{
if (PixFmt == PF_NV12)
{
NumBits = 8;
}
else if (PixFmt == PF_A2B10G10R10)
{
NumBits = 10;
}
else if (PixFmt == PF_P010)
{
NumBits = 16;
}
else
{
NumBits = (8 * GPixelFormats[PixFmt].BlockBytes) / GPixelFormats[PixFmt].NumComponents;
}
}
FVector Off = FVector::Zero();
const FMatrix* Mtx = nullptr;
// Defaults in case no HDR info is present
DisplayMasteringColorSpace.Reset();
DisplayMasteringLuminanceMin = -1.0f;
DisplayMasteringLuminanceMax = -1.0f;
MaxCLL = 0;
MaxFALL = 0;
// Do we have specific HDR information, so we can assume a standard?
if (auto PinnedHDRInfo = HDRInfo.Pin())
{
//
// HDR information present
//
// Mastering display info...
if (auto ColorVolume = PinnedHDRInfo->GetMasteringDisplayColourVolume())
{
// A few sanity checks on the primaries coordinates (by no means exhaustive, but it should catch a fair share of oddities)
if (ColorVolume->display_primaries_x[0] > FMath::Max(ColorVolume->display_primaries_x[1], ColorVolume->display_primaries_x[2]) && // Red has largest X
ColorVolume->display_primaries_y[1] > FMath::Max(ColorVolume->display_primaries_y[0], ColorVolume->display_primaries_y[2]) && // Green has largest Y
ColorVolume->display_primaries_x[2] <= ColorVolume->display_primaries_x[0] && // Blue's X is smaller than Red's
ColorVolume->display_primaries_y[2] <= ColorVolume->display_primaries_y[0] && // Blue's Y is smaller than Red's
ColorVolume->display_primaries_x[2] <= ColorVolume->display_primaries_x[1] && // Blue's X is smaller or same than Green's
ColorVolume->display_primaries_x[1] <= ColorVolume->display_primaries_x[0]) // Red's X is greater or same than Green's
{
DisplayMasteringColorSpace = UE::Color::FColorSpace(FVector2d(ColorVolume->display_primaries_x[0], ColorVolume->display_primaries_y[0]),
FVector2d(ColorVolume->display_primaries_x[1], ColorVolume->display_primaries_y[1]),
FVector2d(ColorVolume->display_primaries_x[2], ColorVolume->display_primaries_y[2]),
FVector2d(ColorVolume->white_point_x, ColorVolume->white_point_y));
}
DisplayMasteringLuminanceMin = ColorVolume->min_display_mastering_luminance;
DisplayMasteringLuminanceMax = ColorVolume->max_display_mastering_luminance;
}
// Content light level info...
if (auto ContentLightLevelInfo = PinnedHDRInfo->GetContentLightLevelInfo())
{
MaxCLL = ContentLightLevelInfo->max_content_light_level;
MaxFALL = ContentLightLevelInfo->max_pic_average_light_level;
}
}
// The sample source color space is always defined by the color primaries value
SourceColorSpace = UE::Color::FColorSpace(ElectraColorimetryUtils::TranslateMPEGColorPrimaries(ColorPrimaries));
// Select the YUV-RGB conversion matrix to use
switch (ElectraColorimetryUtils::TranslateMPEGMatrixCoefficients(MatrixCoefficients))
{
case UE::Color::EColorSpace::None: // ID (RGB)
// no conversion, data is RGB
break;
case UE::Color::EColorSpace::sRGB:
Mtx = bFullRange ? &MediaShaders::YuvToRgbRec709Unscaled : &MediaShaders::YuvToRgbRec709Scaled;
break;
case UE::Color::EColorSpace::Rec2020:
Mtx = bFullRange ? &MediaShaders::YuvToRgbRec2020Unscaled : &MediaShaders::YuvToRgbRec2020Scaled;
break;
default:
check(!"*** Unexpected matrix coefficients!");
Mtx = bFullRange ? &MediaShaders::YuvToRgbRec709Unscaled : &MediaShaders::YuvToRgbRec709Scaled;
}
// Get color encoding (sRGB, linear, PQ, HLG...)
ColorEncoding = ElectraColorimetryUtils::TranslateMPEGTransferCharacteristics(TransferCharacteristics);
if (Mtx)
{
// Select the offsets prior to YUV conversion needed per the incoming data
switch (NumBits)
{
case 8: Off = bFullRange ? MediaShaders::YUVOffsetNoScale8bits : MediaShaders::YUVOffset8bits; break;
case 10: Off = bFullRange ? MediaShaders::YUVOffsetNoScale10bits : MediaShaders::YUVOffset10bits; break;
case 16: Off = bFullRange ? MediaShaders::YUVOffsetNoScale16bits : MediaShaders::YUVOffset16bits; break;
case 32: Off = bFullRange ? MediaShaders::YUVOffsetNoScaleFloat : MediaShaders::YUVOffsetFloat; break;
default: check(!"Unexpected number of bits per channel!");
}
}
// Correctional scale for input data
// (data should be placed in the upper 10-bits of the 16-bit texture channels, but some platforms do not do this - they provide a correctional factor here)
float DataScale = GetSampleDataScale(NumBits == 10);
// Compute scale to make correct towards the max value (P010 will max out at 0xffc0 not 0xffff - so if it is present we need to adjust the scale a bit)
float NormScale = (VideoDecoderOutput->GetFormat() == PF_P010) ? (65535.0f / 65472.0f) : 1.0f;
// Matrix to transform sample data to standard YUV values
FMatrix PreMtx = FMatrix::Identity;
PreMtx.M[0][0] = DataScale * NormScale;
PreMtx.M[1][1] = DataScale * NormScale;
PreMtx.M[2][2] = DataScale * NormScale;
PreMtx.M[0][3] = -Off.X;
PreMtx.M[1][3] = -Off.Y;
PreMtx.M[2][3] = -Off.Z;
// Combine this with the actual YUV-RGB conversion
SampleToRgbMtx = FMatrix44f(Mtx ? (*Mtx * PreMtx) : PreMtx);
// Also store the plain YUV->RGB matrix (pointer) for later reference
YuvToRgbMtx = Mtx;
}
void IElectraTextureSampleBase::InitializePoolable()
{
}
void IElectraTextureSampleBase::ShutdownPoolable()
{
VideoDecoderOutput.Reset();
}
FIntPoint IElectraTextureSampleBase::GetDim() const
{
if (VideoDecoderOutput)
{
return VideoDecoderOutput->GetDim();
}
return FIntPoint::ZeroValue;
}
FIntPoint IElectraTextureSampleBase::GetOutputDim() const
{
if (VideoDecoderOutput)
{
return VideoDecoderOutput->GetOutputDim();
}
return FIntPoint::ZeroValue;
}
FMediaTimeStamp IElectraTextureSampleBase::GetTime() const
{
if (VideoDecoderOutput)
{
const FDecoderTimeStamp TimeStamp = VideoDecoderOutput->GetTime();
return FMediaTimeStamp(TimeStamp.Time, TimeStamp.SequenceIndex);
}
return FMediaTimeStamp();
}
void IElectraTextureSampleBase::SetTime(const FMediaTimeStamp& InTime)
{
if (VideoDecoderOutput)
{
VideoDecoderOutput->SetTime(FDecoderTimeStamp(InTime.Time, InTime.GetIndexValue()));
}
}
FTimespan IElectraTextureSampleBase::GetDuration() const
{
if (VideoDecoderOutput)
{
return VideoDecoderOutput->GetDuration();
}
return FTimespan::Zero();
}
bool IElectraTextureSampleBase::IsOutputSrgb() const
{
return ColorEncoding == UE::Color::EEncoding::sRGB;
}
const FMatrix& IElectraTextureSampleBase::GetYUVToRGBMatrix() const
{
return YuvToRgbMtx ? *YuvToRgbMtx : FMatrix::Identity;
}
bool IElectraTextureSampleBase::GetFullRange() const
{
if (auto PinnedColorimetry = Colorimetry.Pin())
{
return (PinnedColorimetry->GetMPEGDefinition()->VideoFullRangeFlag != 0);
}
return false;
}
FMatrix44f IElectraTextureSampleBase::GetSampleToRGBMatrix() const
{
return SampleToRgbMtx;
}
const UE::Color::FColorSpace& IElectraTextureSampleBase::GetSourceColorSpace() const
{
return SourceColorSpace;
}
UE::Color::EEncoding IElectraTextureSampleBase::GetEncodingType() const
{
return ColorEncoding;
}
float IElectraTextureSampleBase::GetHDRNitsNormalizationFactor() const
{
if (GetEncodingType() == UE::Color::EEncoding::sRGB || GetEncodingType() == UE::Color::EEncoding::Linear)
{
return 1.0f;
}
else
{
return 1.0f / CVarElectraHdrWhiteLevel->GetFloat();
}
}
bool IElectraTextureSampleBase::GetDisplayMasteringLuminance(float& OutMin, float& OutMax) const
{
if (DisplayMasteringLuminanceMin < 0.0f && DisplayMasteringLuminanceMax < 0.0f)
{
return false;
}
OutMin = DisplayMasteringLuminanceMin;
OutMax = DisplayMasteringLuminanceMax;
return true;
}
TOptional<UE::Color::FColorSpace> IElectraTextureSampleBase::GetDisplayMasteringColorSpace() const
{
return DisplayMasteringColorSpace;
}
bool IElectraTextureSampleBase::GetMaxLuminanceLevels(uint16& OutCLL, uint16& OutFALL) const
{
if (MaxCLL == 0 && MaxFALL == 0)
{
return false;
}
OutCLL = MaxCLL;
OutFALL = MaxFALL;
return true;
}
MediaShaders::EToneMapMethod IElectraTextureSampleBase::GetToneMapMethod() const
{
if (GetEncodingType() == UE::Color::EEncoding::sRGB || GetEncodingType() == UE::Color::EEncoding::Linear)
{
return MediaShaders::EToneMapMethod::None;
}
else
{
const int32 ToneMapMethod = FMath::Clamp(CVarElectraHdrToneMapMethod->GetInt(), 0, static_cast<int32>(MediaShaders::EToneMapMethod::MAX) - 1);
return static_cast<MediaShaders::EToneMapMethod>(ToneMapMethod);
}
}
#endif