diff --git a/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs index 27a55e980..7a3dcae76 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs @@ -66,6 +66,12 @@ public class result.Add(HardwareAccelerationKind.Amf); } + // TODO: fix and enable V4L2 M2M + // if (ffmpegCapabilities.HasHardwareAcceleration(HardwareAccelerationMode.V4l2m2m)) + // { + // result.Add(HardwareAccelerationKind.V4l2m2m); + // } + return result; } diff --git a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs index bc8d9426e..54e0f5090 100644 --- a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs +++ b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs @@ -7,5 +7,6 @@ public enum HardwareAccelerationKind Nvenc = 2, Vaapi = 3, VideoToolbox = 4, - Amf = 5 + Amf = 5, + V4l2m2m = 6 } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 7db926ef3..4ae5b7c22 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -1128,6 +1128,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService HardwareAccelerationKind.Vaapi => HardwareAccelerationMode.Vaapi, HardwareAccelerationKind.VideoToolbox => HardwareAccelerationMode.VideoToolbox, HardwareAccelerationKind.Amf => HardwareAccelerationMode.Amf, + HardwareAccelerationKind.V4l2m2m => HardwareAccelerationMode.V4l2m2m, _ => HardwareAccelerationMode.None }; } diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs index a7cec3bd3..6a46d926f 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs @@ -35,6 +35,14 @@ public class FFmpegCapabilities : IFFmpegCapabilities StringComparison.OrdinalIgnoreCase)); } + // V4l2m2m isn't a "hwaccel" in ffmpeg, so check for presence of encoders + if (hardwareAccelerationMode is HardwareAccelerationMode.V4l2m2m) + { + return _ffmpegEncoders.Any(e => e.EndsWith( + $"_{FFmpegKnownHardwareAcceleration.V4l2m2m.Name}", + StringComparison.OrdinalIgnoreCase)); + } + Option maybeAccelToCheck = hardwareAccelerationMode switch { HardwareAccelerationMode.Nvenc => FFmpegKnownHardwareAcceleration.Cuda, diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs index d5d52ca4a..d13e65a9e 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs @@ -11,14 +11,20 @@ public record FFmpegKnownDecoder public static readonly FFmpegKnownDecoder Vp9Cuvid = new("vp9_cuvid"); public static readonly FFmpegKnownDecoder Libdav1d = new("libdav1d"); public static readonly FFmpegKnownDecoder Libaomav1 = new("libaom-av1"); + public static readonly FFmpegKnownDecoder H264V4l2m2m = new("h264_v4l2m2m"); + public static readonly FFmpegKnownDecoder HevcV4l2m2m = new("hevc_v4l2m2m"); + public static readonly FFmpegKnownDecoder Mpeg2V4l2m2m = new("mpeg2_v4l2m2m"); + public static readonly FFmpegKnownDecoder Mpeg4V4l2m2m = new("mpeg4_v4l2m2m"); + public static readonly FFmpegKnownDecoder Vc1V4l2m2m = new("vc1_v4l2m2m"); + public static readonly FFmpegKnownDecoder Vp84V4l2m2m = new("vp8_v4l2m2m"); + public static readonly FFmpegKnownDecoder Vp94V4l2m2m = new("vp9_v4l2m2m"); private FFmpegKnownDecoder(string Name) => this.Name = Name; public string Name { get; } public static IList AllDecoders => - new[] - { + [ Av1Cuvid.Name, H264Cuvid.Name, HevcCuvid.Name, @@ -26,7 +32,27 @@ public record FFmpegKnownDecoder Mpeg4Cuvid.Name, Vc1Cuvid.Name, Vp9Cuvid.Name, + Libdav1d.Name, - Libaomav1.Name - }; + Libaomav1.Name, + + H264V4l2m2m.Name, + HevcV4l2m2m.Name, + Mpeg2V4l2m2m.Name, + Mpeg4V4l2m2m.Name, + Vc1V4l2m2m.Name, + Vp84V4l2m2m.Name, + Vp94V4l2m2m.Name + ]; + + public static IList V4l2m2mDecoders => + [ + H264V4l2m2m.Name, + HevcV4l2m2m.Name, + Mpeg2V4l2m2m.Name, + Mpeg4V4l2m2m.Name, + Vc1V4l2m2m.Name, + Vp84V4l2m2m.Name, + Vp94V4l2m2m.Name + ]; } diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs index 3ef1b9804..34f2baa67 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs @@ -13,6 +13,8 @@ public record FFmpegKnownEncoder [ "h264_amf", "hevc_amf", + "h264_v4l2m2m", + "hevc_v4l2m2m", "h264_videotoolbox", "hevc_videotoolbox" ]; diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs index 3d273e6f1..adf36bd7d 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs @@ -9,6 +9,7 @@ public record FFmpegKnownHardwareAcceleration public static readonly FFmpegKnownHardwareAcceleration VideoToolbox = new("videotoolbox"); public static readonly FFmpegKnownHardwareAcceleration OpenCL = new("opencl"); public static readonly FFmpegKnownHardwareAcceleration Vulkan = new("vulkan"); + public static readonly FFmpegKnownHardwareAcceleration V4l2m2m = new("v4l2m2m"); private FFmpegKnownHardwareAcceleration(string Name) => this.Name = Name; @@ -22,6 +23,7 @@ public record FFmpegKnownHardwareAcceleration Vaapi.Name, VideoToolbox.Name, OpenCL.Name, - Vulkan.Name + Vulkan.Name, + V4l2m2m.Name ]; } diff --git a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs index 1ea672ffa..eaf919207 100644 --- a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs +++ b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs @@ -109,6 +109,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory HardwareAccelerationMode.Vaapi => await GetVaapiCapabilities(vaapiDisplay, vaapiDriver, vaapiDevice), HardwareAccelerationMode.VideoToolbox => new VideoToolboxHardwareCapabilities(ffmpegCapabilities, _logger), HardwareAccelerationMode.Amf => new AmfHardwareCapabilities(), + HardwareAccelerationMode.V4l2m2m => new V4l2m2mHardwareCapabilities(ffmpegCapabilities), _ => new DefaultHardwareCapabilities() }; } diff --git a/ErsatzTV.FFmpeg/Capabilities/V4l2m2mHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/V4l2m2mHardwareCapabilities.cs new file mode 100644 index 000000000..82a99df8d --- /dev/null +++ b/ErsatzTV.FFmpeg/Capabilities/V4l2m2mHardwareCapabilities.cs @@ -0,0 +1,67 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Capabilities; + +public class V4l2m2mHardwareCapabilities(IFFmpegCapabilities ffmpegCapabilities) : IHardwareCapabilities +{ + public FFmpegCapability CanDecode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat, + bool isHdr) + { + int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); + + return (videoFormat, bitDepth) switch + { + (VideoFormat.H264, 8) => ffmpegCapabilities.HasDecoder(FFmpegKnownDecoder.H264V4l2m2m) + ? FFmpegCapability.Hardware + : FFmpegCapability.Software, + + (VideoFormat.Hevc, _) => ffmpegCapabilities.HasDecoder(FFmpegKnownDecoder.HevcV4l2m2m) + ? FFmpegCapability.Hardware + : FFmpegCapability.Software, + + (VideoFormat.Mpeg2Video, 8) => ffmpegCapabilities.HasDecoder(FFmpegKnownDecoder.Mpeg2V4l2m2m) + ? FFmpegCapability.Hardware + : FFmpegCapability.Software, + + (VideoFormat.Mpeg4, _) => ffmpegCapabilities.HasDecoder(FFmpegKnownDecoder.Mpeg4V4l2m2m) + ? FFmpegCapability.Hardware + : FFmpegCapability.Software, + + (VideoFormat.Vc1, _) => ffmpegCapabilities.HasDecoder(FFmpegKnownDecoder.Vc1V4l2m2m) + ? FFmpegCapability.Hardware + : FFmpegCapability.Software, + + (VideoFormat.Vp8, _) => ffmpegCapabilities.HasDecoder(FFmpegKnownDecoder.Vp84V4l2m2m) + ? FFmpegCapability.Hardware + : FFmpegCapability.Software, + + (VideoFormat.Vp9, _) => ffmpegCapabilities.HasDecoder(FFmpegKnownDecoder.Vp94V4l2m2m) + ? FFmpegCapability.Hardware + : FFmpegCapability.Software, + + _ => FFmpegCapability.Software + }; + } + + public FFmpegCapability CanEncode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) + { + int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); + + return (videoFormat, bitDepth) switch + { + // 10-bit h264 encoding is not support by any hardware + (VideoFormat.H264, 10) => FFmpegCapability.Software, + + _ => FFmpegCapability.Hardware + }; + } + + public Option GetRateControlMode(string videoFormat, Option maybePixelFormat) => + Option.None; +} diff --git a/ErsatzTV.FFmpeg/Decoder/DecoderV4l2m2m.cs b/ErsatzTV.FFmpeg/Decoder/DecoderV4l2m2m.cs new file mode 100644 index 000000000..204fd60b1 --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/DecoderV4l2m2m.cs @@ -0,0 +1,8 @@ +namespace ErsatzTV.FFmpeg.Decoder; + +public class DecoderV4l2m2m : DecoderBase +{ + protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; + public override string Name => "implicit_v4l2m2m"; + public override string[] InputOptions(InputFile inputFile) => []; +} diff --git a/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderH264V4l2m2m.cs b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderH264V4l2m2m.cs new file mode 100644 index 000000000..e47fc6c5c --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderH264V4l2m2m.cs @@ -0,0 +1,19 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Decoder.V4l2m2m; + +public class DecoderH264V4l2m2m : DecoderBase +{ + public override string Name => "h264_v4l2m2m"; + + protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; + + public override FrameState NextState(FrameState currentState) + { + FrameState nextState = base.NextState(currentState); + + return currentState.PixelFormat.Match( + pixelFormat => nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) }, + () => nextState); + } +} diff --git a/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderHevcV4l2m2m.cs b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderHevcV4l2m2m.cs new file mode 100644 index 000000000..303ffdce3 --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderHevcV4l2m2m.cs @@ -0,0 +1,27 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Decoder.V4l2m2m; + +public class DecoderHevcV4l2m2m : DecoderBase +{ + public override string Name => "hevc_v4l2m2m"; + + protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; + + public override FrameState NextState(FrameState currentState) + { + FrameState nextState = base.NextState(currentState); + + return currentState.PixelFormat.Match( + pixelFormat => + { + if (pixelFormat.BitDepth == 10) + { + return nextState with { PixelFormat = new PixelFormatP010() }; + } + + return nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) }; + }, + () => nextState); + } +} diff --git a/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderMpeg2V4l2m2m.cs b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderMpeg2V4l2m2m.cs new file mode 100644 index 000000000..58516542c --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderMpeg2V4l2m2m.cs @@ -0,0 +1,8 @@ +namespace ErsatzTV.FFmpeg.Decoder.V4l2m2m; + +public class DecoderMpeg2V4l2m2m : DecoderBase +{ + public override string Name => "mpeg2_v4l2m2m"; + + protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; +} diff --git a/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderMpeg4V4l2m2m.cs b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderMpeg4V4l2m2m.cs new file mode 100644 index 000000000..6dce6e0af --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderMpeg4V4l2m2m.cs @@ -0,0 +1,8 @@ +namespace ErsatzTV.FFmpeg.Decoder.V4l2m2m; + +public class DecoderMpeg4V4l2m2m : DecoderBase +{ + public override string Name => "mpeg4_v4l2m2m"; + + protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; +} diff --git a/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderVc1V4l2m2m.cs b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderVc1V4l2m2m.cs new file mode 100644 index 000000000..c1c025cd9 --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderVc1V4l2m2m.cs @@ -0,0 +1,8 @@ +namespace ErsatzTV.FFmpeg.Decoder.V4l2m2m; + +public class DecoderVc1V4l2m2m : DecoderBase +{ + public override string Name => "vc1_v4l2m2m"; + + protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; +} diff --git a/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderVp8V4l2m2m.cs b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderVp8V4l2m2m.cs new file mode 100644 index 000000000..342acc04c --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderVp8V4l2m2m.cs @@ -0,0 +1,27 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Decoder.V4l2m2m; + +public class DecoderVp8V4l2m2m : DecoderBase +{ + public override string Name => "vp8_v4l2m2m"; + + protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; + + public override FrameState NextState(FrameState currentState) + { + FrameState nextState = base.NextState(currentState); + + return currentState.PixelFormat.Match( + pixelFormat => + { + if (pixelFormat.BitDepth == 10) + { + return nextState with { PixelFormat = new PixelFormatP010() }; + } + + return nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) }; + }, + () => nextState); + } +} diff --git a/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderVp9V4l2m2m.cs b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderVp9V4l2m2m.cs new file mode 100644 index 000000000..7e7624f63 --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/V4l2m2m/DecoderVp9V4l2m2m.cs @@ -0,0 +1,27 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Decoder.V4l2m2m; + +public class DecoderVp9V4l2m2m : DecoderBase +{ + public override string Name => "vp9_v4l2m2m"; + + protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; + + public override FrameState NextState(FrameState currentState) + { + FrameState nextState = base.NextState(currentState); + + return currentState.PixelFormat.Match( + pixelFormat => + { + if (pixelFormat.BitDepth == 10) + { + return nextState with { PixelFormat = new PixelFormatP010() }; + } + + return nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) }; + }, + () => nextState); + } +} diff --git a/ErsatzTV.FFmpeg/Encoder/V4l2m2m/EncoderH264V4l2m2m.cs b/ErsatzTV.FFmpeg/Encoder/V4l2m2m/EncoderH264V4l2m2m.cs new file mode 100644 index 000000000..18df85568 --- /dev/null +++ b/ErsatzTV.FFmpeg/Encoder/V4l2m2m/EncoderH264V4l2m2m.cs @@ -0,0 +1,21 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Encoder.V4l2m2m; + +public class EncoderH264V4l2m2m : EncoderBase +{ + public override string Name => "h264_v4l2m2m"; + public override StreamKind Kind => StreamKind.Video; + + public override string[] OutputOptions => + [ + "-c:v", Name, + "-num_capture_buffers", "16" + ]; + + public override FrameState NextState(FrameState currentState) => currentState with + { + VideoFormat = VideoFormat.H264, + FrameDataLocation = FrameDataLocation.Hardware + }; +} diff --git a/ErsatzTV.FFmpeg/Encoder/V4l2m2m/EncoderHevcV4l2m2m.cs b/ErsatzTV.FFmpeg/Encoder/V4l2m2m/EncoderHevcV4l2m2m.cs new file mode 100644 index 000000000..cac569851 --- /dev/null +++ b/ErsatzTV.FFmpeg/Encoder/V4l2m2m/EncoderHevcV4l2m2m.cs @@ -0,0 +1,15 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Encoder.V4l2m2m; + +public class EncoderHevcV4l2m2m : EncoderBase +{ + public override string Name => "hevc_v4l2m2m"; + public override StreamKind Kind => StreamKind.Video; + + public override FrameState NextState(FrameState currentState) => currentState with + { + VideoFormat = VideoFormat.Hevc, + FrameDataLocation = FrameDataLocation.Hardware + }; +} diff --git a/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs b/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs index f51512cbc..092316352 100644 --- a/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs @@ -28,6 +28,9 @@ public class SubtitleHardwareUploadFilter : BaseFilter // leave amf in software since we use a software overlay filter HardwareAccelerationMode.Amf => string.Empty, + // leave v4l2m2m in software since we use a software overlay filter + HardwareAccelerationMode.V4l2m2m => string.Empty, + _ => "hwupload" }; diff --git a/ErsatzTV.FFmpeg/Filter/WatermarkHardwareUploadFilter.cs b/ErsatzTV.FFmpeg/Filter/WatermarkHardwareUploadFilter.cs index 3ce497ef1..77d81e154 100644 --- a/ErsatzTV.FFmpeg/Filter/WatermarkHardwareUploadFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/WatermarkHardwareUploadFilter.cs @@ -27,6 +27,9 @@ public class WatermarkHardwareUploadFilter : BaseFilter // leave amf in software since we use a software overlay filter HardwareAccelerationMode.Amf => string.Empty, + // leave v4l2m2m in software since we use a software overlay filter + HardwareAccelerationMode.V4l2m2m => string.Empty, + _ => "hwupload" }; diff --git a/ErsatzTV.FFmpeg/Format/VideoFormat.cs b/ErsatzTV.FFmpeg/Format/VideoFormat.cs index fd94d7b6c..76b3df7ac 100644 --- a/ErsatzTV.FFmpeg/Format/VideoFormat.cs +++ b/ErsatzTV.FFmpeg/Format/VideoFormat.cs @@ -10,6 +10,7 @@ public static class VideoFormat public const string MsMpeg4V3 = "msmpeg4v3"; public const string Vc1 = "vc1"; public const string Mpeg4 = "mpeg4"; + public const string Vp8 = "vp8"; public const string Vp9 = "vp9"; public const string Av1 = "av1"; public const string MpegTs = "mpegts"; diff --git a/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs b/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs index b28aba0e3..d3ff43823 100644 --- a/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs +++ b/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs @@ -9,5 +9,6 @@ public enum HardwareAccelerationMode VideoToolbox = 4, Amf = 5, OpenCL = 6, - Vulkan = 7 + Vulkan = 7, + V4l2m2m = 8 } diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs index 30f7f0a6a..70327417f 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs @@ -116,6 +116,21 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory fontsFolder, _logger), + HardwareAccelerationMode.V4l2m2m when capabilities is not NoHardwareCapabilities => new + V4l2m2mPipelineBuilder( + ffmpegCapabilities, + capabilities, + hardwareAccelerationMode, + videoInputFile, + audioInputFile, + watermarkInputFile, + subtitleInputFile, + concatInputFile, + graphicsEngineInput, + reportsFolder, + fontsFolder, + _logger), + HardwareAccelerationMode.Amf when capabilities is not NoHardwareCapabilities => new AmfPipelineBuilder( ffmpegCapabilities, capabilities, diff --git a/ErsatzTV.FFmpeg/Pipeline/V4l2m2mPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/V4l2m2mPipelineBuilder.cs new file mode 100644 index 000000000..9f52e31d1 --- /dev/null +++ b/ErsatzTV.FFmpeg/Pipeline/V4l2m2mPipelineBuilder.cs @@ -0,0 +1,158 @@ +using ErsatzTV.FFmpeg.Capabilities; +using ErsatzTV.FFmpeg.Decoder; +using ErsatzTV.FFmpeg.Decoder.V4l2m2m; +using ErsatzTV.FFmpeg.Encoder; +using ErsatzTV.FFmpeg.Encoder.V4l2m2m; +using ErsatzTV.FFmpeg.Filter; +using ErsatzTV.FFmpeg.Format; +using ErsatzTV.FFmpeg.OutputFormat; +using ErsatzTV.FFmpeg.OutputOption; +using Microsoft.Extensions.Logging; + +namespace ErsatzTV.FFmpeg.Pipeline; + +public class V4l2m2mPipelineBuilder : SoftwarePipelineBuilder +{ + private readonly IHardwareCapabilities _hardwareCapabilities; + private readonly ILogger _logger; + + public V4l2m2mPipelineBuilder( + IFFmpegCapabilities ffmpegCapabilities, + IHardwareCapabilities hardwareCapabilities, + HardwareAccelerationMode hardwareAccelerationMode, + Option videoInputFile, + Option audioInputFile, + Option watermarkInputFile, + Option subtitleInputFile, + Option concatInputFile, + Option graphicsEngineInput, + string reportsFolder, + string fontsFolder, + ILogger logger) : base( + ffmpegCapabilities, + hardwareAccelerationMode, + videoInputFile, + audioInputFile, + watermarkInputFile, + subtitleInputFile, + concatInputFile, + graphicsEngineInput, + reportsFolder, + fontsFolder, + logger) + { + _hardwareCapabilities = hardwareCapabilities; + _logger = logger; + } + + protected override FFmpegState SetAccelState( + VideoStream videoStream, + FFmpegState ffmpegState, + FrameState desiredState, + PipelineContext context, + ICollection pipelineSteps) + { + FFmpegCapability decodeCapability = _hardwareCapabilities.CanDecode( + videoStream.Codec, + videoStream.Profile, + videoStream.PixelFormat, + videoStream.ColorParams.IsHdr); + FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode( + desiredState.VideoFormat, + desiredState.VideoProfile, + desiredState.PixelFormat); + + // use software encoding (rawvideo) when piping to parent hls segmenter + if (ffmpegState.OutputFormat is OutputFormatKind.Nut) + { + encodeCapability = FFmpegCapability.Software; + } + + // disable hw accel if decoder/encoder isn't supported + return ffmpegState with + { + DecoderHardwareAccelerationMode = decodeCapability == FFmpegCapability.Hardware + ? HardwareAccelerationMode.V4l2m2m + : HardwareAccelerationMode.None, + EncoderHardwareAccelerationMode = encodeCapability == FFmpegCapability.Hardware + ? HardwareAccelerationMode.V4l2m2m + : HardwareAccelerationMode.None + }; + } + + protected override Option SetDecoder( + VideoInputFile videoInputFile, + VideoStream videoStream, + FFmpegState ffmpegState, + PipelineContext context) + { + Option maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch + { + (HardwareAccelerationMode.V4l2m2m, VideoFormat.Hevc) => new DecoderHevcV4l2m2m(), + (HardwareAccelerationMode.V4l2m2m, VideoFormat.H264) => new DecoderH264V4l2m2m(), + (HardwareAccelerationMode.V4l2m2m, VideoFormat.Mpeg2Video) => new DecoderMpeg2V4l2m2m(), + (HardwareAccelerationMode.V4l2m2m, VideoFormat.Mpeg4) => new DecoderMpeg4V4l2m2m(), + (HardwareAccelerationMode.V4l2m2m, VideoFormat.Vc1) => new DecoderVc1V4l2m2m(), + (HardwareAccelerationMode.V4l2m2m, VideoFormat.Vp8) => new DecoderVp8V4l2m2m(), + (HardwareAccelerationMode.V4l2m2m, VideoFormat.Vp9) => new DecoderVp9V4l2m2m(), + + _ => GetSoftwareDecoder(videoStream) + }; + + foreach (IDecoder decoder in maybeDecoder) + { + videoInputFile.AddOption(decoder); + return Some(decoder); + } + + return None; + } + + protected override Option GetEncoder( + FFmpegState ffmpegState, + FrameState currentState, + FrameState desiredState) => + (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch + { + (HardwareAccelerationMode.V4l2m2m, VideoFormat.Hevc) => + new EncoderHevcV4l2m2m(), + (HardwareAccelerationMode.V4l2m2m, VideoFormat.H264) => + new EncoderH264V4l2m2m(), + + _ => GetSoftwareEncoder(ffmpegState, currentState, desiredState) + }; + + protected override List SetPixelFormat( + VideoStream videoStream, + Option desiredPixelFormat, + FrameState currentState, + ICollection pipelineSteps) + { + var result = new List(); + + foreach (IPixelFormat pixelFormat in desiredPixelFormat) + { + if (!videoStream.ColorParams.IsBt709) + { + // _logger.LogDebug("Adding colorspace filter"); + var colorspace = new ColorspaceFilter(currentState, videoStream, pixelFormat); + currentState = colorspace.NextState(currentState); + result.Add(colorspace); + } + + if (currentState.PixelFormat.Map(f => f.FFmpegName) != pixelFormat.FFmpegName) + { + _logger.LogDebug( + "Format {A} doesn't equal {B}", + currentState.PixelFormat.Map(f => f.FFmpegName), + pixelFormat.FFmpegName); + + //result.Add(new PixelFormatFilter(pixelFormat)); + } + + pipelineSteps.Add(new PixelFormatOutputOption(pixelFormat)); + } + + return result; + } +} diff --git a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs index 0bc11ff4f..0bbe260c9 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs @@ -132,19 +132,23 @@ public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAcceler } } - if (_runtimeInfo.IsOSPlatform(OSPlatform.Windows)) + // not real ffmpeg hwaccels, but have hw encoders that we can use + string output2 = await GetProcessOutput( + ffmpegPath, + FFmpegEncodersArguments, + cancellationToken); + foreach (string encoder in output2.Split("\n").Map(s => s.Trim())) { - string output2 = await GetProcessOutput( - ffmpegPath, - FFmpegEncodersArguments, - cancellationToken); - foreach (string method in output2.Split("\n").Map(s => s.Trim())) + if (_runtimeInfo.IsOSPlatform(OSPlatform.Windows) && encoder.Contains("_amf ")) { - if (method.Contains("_amf ")) - { - result.Add(HardwareAccelerationKind.Amf); - } + result.Add(HardwareAccelerationKind.Amf); } + + // TODO: fix and enable V4L2 M2M + // else if (_runtimeInfo.IsOSPlatform(OSPlatform.Linux) && encoder.Contains("_v4l2m2m ")) + // { + // result.Add(HardwareAccelerationKind.V4l2m2m); + // } } return result.ToList(); diff --git a/ErsatzTV/Pages/FFmpegEditor.razor b/ErsatzTV/Pages/FFmpegEditor.razor index feb645e76..ef0e93ac5 100644 --- a/ErsatzTV/Pages/FFmpegEditor.razor +++ b/ErsatzTV/Pages/FFmpegEditor.razor @@ -430,6 +430,7 @@ HardwareAccelerationKind.Qsv => HardwareAccelerationMode.Qsv, HardwareAccelerationKind.Vaapi => HardwareAccelerationMode.Vaapi, HardwareAccelerationKind.VideoToolbox => HardwareAccelerationMode.VideoToolbox, + HardwareAccelerationKind.V4l2m2m => HardwareAccelerationMode.V4l2m2m, _ => HardwareAccelerationMode.None }; diff --git a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs index 9683f6aa7..5bd96f88a 100644 --- a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs +++ b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs @@ -40,6 +40,11 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator V4l2m2mFormats = + [ + FFmpegProfileVideoFormat.H264, + FFmpegProfileVideoFormat.Hevc + ]; public FFmpegProfileEditViewModelValidator() { RuleFor(x => x.Name).NotEmpty(); @@ -93,6 +98,14 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator x.HardwareAcceleration == HardwareAccelerationKind.V4l2m2m, + () => + { + RuleFor(x => x.VideoFormat).Must(c => V4l2m2mFormats.Contains(c)) + .WithMessage("V4L2 M2M supports formats (h264, hevc)"); + }); + When( x => x.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video, () => RuleFor(x => x.BitDepth)