diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d2656088..80d19f6e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - work around buggy ffmpeg behavior where hevc_vaapi encoder with RadeonSI driver incorrectly outputs height of 1088 instead of 1080 - fix green padding when encoding h264 using main profile - Automatically kill playback troubleshooting ffmpeg process if it hasn't completed after two minutes +- Fix playback of certain BT.2020 content ### Changed - No longer round framerate to nearest integer when normalizing framerate diff --git a/ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs index 3430141b2..f8eeff720 100644 --- a/ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs @@ -8,7 +8,7 @@ public class AmfHardwareCapabilities : IHardwareCapabilities string videoFormat, Option videoProfile, Option maybePixelFormat, - bool isHdr) => FFmpegCapability.Software; + ColorParams colorParams) => FFmpegCapability.Software; public FFmpegCapability CanEncode( string videoFormat, diff --git a/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs index 439d472c2..c4101723a 100644 --- a/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs @@ -8,7 +8,7 @@ public class DefaultHardwareCapabilities : IHardwareCapabilities string videoFormat, Option videoProfile, Option maybePixelFormat, - bool isHdr) + ColorParams colorParams) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); @@ -17,6 +17,8 @@ public class DefaultHardwareCapabilities : IHardwareCapabilities // 10-bit h264 decoding is likely not support by any hardware (VideoFormat.H264, 10) => FFmpegCapability.Software, + (_, _) when colorParams.IsBt2020Ten => FFmpegCapability.Software, + _ => FFmpegCapability.Hardware }; } diff --git a/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs index fe95e769c..19adece1f 100644 --- a/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs @@ -8,7 +8,7 @@ public interface IHardwareCapabilities string videoFormat, Option videoProfile, Option maybePixelFormat, - bool isHdr); + ColorParams colorParams); FFmpegCapability CanEncode( string videoFormat, diff --git a/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs index a3784ec5e..0baafe62a 100644 --- a/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs @@ -8,7 +8,7 @@ public class NoHardwareCapabilities : IHardwareCapabilities string videoFormat, Option videoProfile, Option maybePixelFormat, - bool isHdr) => + ColorParams colorParams) => FFmpegCapability.Software; public FFmpegCapability CanEncode( diff --git a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs index c4227a7c6..2f9c9bf22 100644 --- a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs @@ -15,7 +15,7 @@ public class NvidiaHardwareCapabilities(CudaDevice cudaDevice, IFFmpegCapabiliti string videoFormat, Option videoProfile, Option maybePixelFormat, - bool isHdr) + ColorParams colorParams) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); @@ -52,6 +52,11 @@ public class NvidiaHardwareCapabilities(CudaDevice cudaDevice, IFFmpegCapabiliti } } + if (colorParams.IsBt2020Ten) + { + isHardware = false; + } + if (isHardware) { return videoFormat switch diff --git a/ErsatzTV.FFmpeg/Capabilities/RkmppHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/RkmppHardwareCapabilities.cs index f5065e178..1c67a7e6a 100644 --- a/ErsatzTV.FFmpeg/Capabilities/RkmppHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/RkmppHardwareCapabilities.cs @@ -8,7 +8,7 @@ public class RkmppHardwareCapabilities : IHardwareCapabilities string videoFormat, Option videoProfile, Option maybePixelFormat, - bool isHdr) => FFmpegCapability.Hardware; + ColorParams colorParams) => FFmpegCapability.Hardware; public FFmpegCapability CanEncode( string videoFormat, @@ -31,4 +31,4 @@ public class RkmppHardwareCapabilities : IHardwareCapabilities public Option GetRateControlMode(string videoFormat, Option maybePixelFormat) => Option.None; -} \ No newline at end of file +} diff --git a/ErsatzTV.FFmpeg/Capabilities/V4l2m2mHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/V4l2m2mHardwareCapabilities.cs index 82a99df8d..7ff052b42 100644 --- a/ErsatzTV.FFmpeg/Capabilities/V4l2m2mHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/V4l2m2mHardwareCapabilities.cs @@ -8,12 +8,14 @@ public class V4l2m2mHardwareCapabilities(IFFmpegCapabilities ffmpegCapabilities) string videoFormat, Option videoProfile, Option maybePixelFormat, - bool isHdr) + ColorParams colorParams) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); return (videoFormat, bitDepth) switch { + (_, _) when colorParams.IsBt2020Ten => FFmpegCapability.Software, + (VideoFormat.H264, 8) => ffmpegCapabilities.HasDecoder(FFmpegKnownDecoder.H264V4l2m2m) ? FFmpegCapability.Hardware : FFmpegCapability.Software, @@ -43,7 +45,7 @@ public class V4l2m2mHardwareCapabilities(IFFmpegCapabilities ffmpegCapabilities) : FFmpegCapability.Software, _ => FFmpegCapability.Software - }; + }; } public FFmpegCapability CanEncode( diff --git a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs index 1733715a6..79d53e8a1 100644 --- a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs @@ -18,12 +18,14 @@ public class VaapiHardwareCapabilities( string videoFormat, Option videoProfile, Option maybePixelFormat, - bool isHdr) + ColorParams colorParams) { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); bool isHardware = (videoFormat, videoProfile.IfNone(string.Empty).ToLowerInvariant()) switch { + (_, _) when colorParams.IsBt2020Ten => false, + // no hardware decoding of 10-bit h264 (VideoFormat.H264, _) when bitDepth == 10 => false, diff --git a/ErsatzTV.FFmpeg/Capabilities/VideoToolboxHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/VideoToolboxHardwareCapabilities.cs index b74f1017c..381ff525b 100644 --- a/ErsatzTV.FFmpeg/Capabilities/VideoToolboxHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/VideoToolboxHardwareCapabilities.cs @@ -24,7 +24,7 @@ public class VideoToolboxHardwareCapabilities : IHardwareCapabilities string videoFormat, Option videoProfile, Option maybePixelFormat, - bool isHdr) + ColorParams colorParams) { if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && Decoders.IsEmpty) { @@ -65,6 +65,8 @@ public class VideoToolboxHardwareCapabilities : IHardwareCapabilities // 10-bit h264 decoding is likely not support by any hardware (VideoFormat.H264, 10) => FFmpegCapability.Software, + (_, _) when colorParams.IsBt2020Ten => FFmpegCapability.Software, + _ => Decoders.ContainsKey(videoFormat) ? FFmpegCapability.Hardware : FFmpegCapability.Software }; } diff --git a/ErsatzTV.FFmpeg/ColorParams.cs b/ErsatzTV.FFmpeg/ColorParams.cs index f84d61ffe..d0c3f0f83 100644 --- a/ErsatzTV.FFmpeg/ColorParams.cs +++ b/ErsatzTV.FFmpeg/ColorParams.cs @@ -30,4 +30,6 @@ public record ColorParams(string ColorRange, string ColorSpace, string ColorTran && string.IsNullOrWhiteSpace(ColorPrimaries); } } + + public bool IsBt2020Ten => ColorTransfer is "bt2020-10"; } diff --git a/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs b/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs index 369e687fa..7ee40617e 100644 --- a/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs @@ -75,7 +75,7 @@ public class ColorspaceFilter : BaseFilter } string inputOverrides = string.Empty; - if (cp.IsMixed || _forceInputOverrides) + if (!cp.IsBt2020Ten && (cp.IsMixed || _forceInputOverrides)) { string range = string.IsNullOrWhiteSpace(cp.ColorRange) ? string.Empty diff --git a/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs index 2525941ac..8daad41dd 100644 --- a/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs @@ -54,7 +54,7 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder videoStream.Codec, videoStream.Profile, videoStream.PixelFormat, - videoStream.ColorParams.IsHdr); + videoStream.ColorParams); FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode( desiredState.VideoFormat, desiredState.VideoProfile, diff --git a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs index 3d657805c..5aaa1ee83 100644 --- a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs @@ -62,7 +62,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder videoStream.Codec, videoStream.Profile, videoStream.PixelFormat, - videoStream.ColorParams.IsHdr); + videoStream.ColorParams); FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode( desiredState.VideoFormat, desiredState.VideoProfile, diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index 9eb3af6cd..f4d7e6bd2 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -589,6 +589,20 @@ public abstract class PipelineBuilderBase : IPipelineBuilder ? None : SetDecoder(videoInputFile, videoStream, ffmpegState, context); + Option videoStreamPixelFormat = videoStream.PixelFormat; + + // bt.2020 needs to immediately change to bt709 and a new format + if (videoStream.ColorParams.IsBt2020Ten) + { + IPixelFormat targetFormat = desiredState.PixelFormat.IfNone(new PixelFormatYuv420P()); + + videoInputFile.FilterSteps.Add(new ColorspaceFilter(desiredState, videoStream, targetFormat)); + + // update pipeline to have accurate color params and pixel format + videoStream.ResetColorParams(ColorParams.Default); + videoStreamPixelFormat = Some(targetFormat); + } + //SetStillImageInfiniteLoop(videoInputFile, videoStream, ffmpegState); SetRealtimeInput(videoInputFile, desiredState); SetInfiniteLoop(videoInputFile, videoStream, ffmpegState, desiredState); @@ -603,7 +617,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder FilterChain filterChain = SetVideoFilters( videoInputFile, - videoStream, + videoStream with { PixelFormat = videoStreamPixelFormat }, _watermarkInputFile, _subtitleInputFile, _graphicsEngineInput, diff --git a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs index bc5595545..88ca70040 100644 --- a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs @@ -65,7 +65,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder videoStream.Codec, videoStream.Profile, videoStream.PixelFormat, - videoStream.ColorParams.IsHdr); + videoStream.ColorParams); FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode( desiredState.VideoFormat, desiredState.VideoProfile, diff --git a/ErsatzTV.FFmpeg/Pipeline/RkmppPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/RkmppPipelineBuilder.cs index 6c559d312..0113706e1 100644 --- a/ErsatzTV.FFmpeg/Pipeline/RkmppPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/RkmppPipelineBuilder.cs @@ -57,7 +57,7 @@ public class RkmppPipelineBuilder : SoftwarePipelineBuilder videoStream.Codec, videoStream.Profile, videoStream.PixelFormat, - videoStream.ColorParams.IsHdr); + videoStream.ColorParams); FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode( desiredState.VideoFormat, desiredState.VideoProfile, diff --git a/ErsatzTV.FFmpeg/Pipeline/V4l2m2mPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/V4l2m2mPipelineBuilder.cs index 9f52e31d1..b71ee0805 100644 --- a/ErsatzTV.FFmpeg/Pipeline/V4l2m2mPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/V4l2m2mPipelineBuilder.cs @@ -56,7 +56,7 @@ public class V4l2m2mPipelineBuilder : SoftwarePipelineBuilder videoStream.Codec, videoStream.Profile, videoStream.PixelFormat, - videoStream.ColorParams.IsHdr); + videoStream.ColorParams); FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode( desiredState.VideoFormat, desiredState.VideoProfile, diff --git a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs index e61cdec3e..af1ec1962 100644 --- a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs @@ -68,7 +68,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder videoStream.Codec, videoStream.Profile, videoStream.PixelFormat, - videoStream.ColorParams.IsHdr); + videoStream.ColorParams); FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode( desiredState.VideoFormat, desiredState.VideoProfile, diff --git a/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs index 0dbd0996d..dd03af6b9 100644 --- a/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs @@ -56,7 +56,7 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder videoStream.Codec, videoStream.Profile, videoStream.PixelFormat, - videoStream.ColorParams.IsHdr); + videoStream.ColorParams); FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode( desiredState.VideoFormat, desiredState.VideoProfile,