From 2ddcfd2edb0d3f068fc912187587ff25b063372a Mon Sep 17 00:00:00 2001 From: Samuele Lorefice Date: Thu, 18 Dec 2025 02:11:52 +0100 Subject: [PATCH] Added SW encoding strategies --- Encoder/EncoderService.cs | 19 ++++-- Encoder/Strategies.cs | 137 +++++++++++++++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 8 deletions(-) diff --git a/Encoder/EncoderService.cs b/Encoder/EncoderService.cs index 2d8abbf..490869c 100644 --- a/Encoder/EncoderService.cs +++ b/Encoder/EncoderService.cs @@ -89,7 +89,7 @@ public class EncoderService : BackgroundService, IEncoderService { FileName = Path.Combine(options.FfmpegPath, "ffmpeg.exe"), Arguments = @"-hide_banner -init_hw_device ""list""", CreateNoWindow = true, - UseShellExecute = true, + UseShellExecute = false, RedirectStandardOutput = true }; using Process ffmpeg = Process.Start(psi)!; @@ -97,12 +97,21 @@ public class EncoderService : BackgroundService, IEncoderService { await ffmpeg.WaitForExitAsync(cancellationToken); bool nvenc = output.Contains("cuda"); - IEncodingStrategy strategy = job.RequestedEncoding switch { - EncoderType.H264 => new H264EncodingStrategy(file, outputPath, job), - EncoderType.HEVC => new HEVCEncodingStrategy(file, outputPath, job), - EncoderType.AV1 => new AV1EncodingStrategy(file, outputPath, job), + IEncodingStrategy strategy; + + if(nvenc) strategy= job.RequestedEncoding switch { + EncoderType.H264 => new H264NvencEncodingStrategy(file, outputPath, job), + EncoderType.HEVC => new HevcNvencEncodingStrategy(file, outputPath, job), + EncoderType.AV1 => new AV1NvencEncodingStrategy(file, outputPath, job), _ => throw new ArgumentOutOfRangeException(nameof(job), job, null) }; + else strategy = job.RequestedEncoding switch { + EncoderType.H264 => new H264SwEncodingStrategy(file, outputPath, job), + EncoderType.HEVC => new HevcSwEncodingStrategy(file, outputPath, job), + EncoderType.AV1 => new AV1SwEncodingStrategy(file, outputPath, job), + _ => throw new ArgumentOutOfRangeException(nameof(job), job, null) + }; + bool result = await strategy.ExecuteAsync(cancellationToken, Progress); if(result) { diff --git a/Encoder/Strategies.cs b/Encoder/Strategies.cs index bd26381..3c3a9b5 100644 --- a/Encoder/Strategies.cs +++ b/Encoder/Strategies.cs @@ -23,7 +23,7 @@ public interface IEncodingStrategy { } [EncodingStrategy(EncoderType.AV1)] -public class AV1EncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy { +public class AV1NvencEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy { public string InputFilePath { get; set; } = inputFilePath; public string OutputFilePath { get; set; } = outputFilePath; @@ -67,7 +67,7 @@ public class AV1EncodingStrategy(string inputFilePath, string outputFilePath, En } [EncodingStrategy(EncoderType.HEVC)] -public class HEVCEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy { +public class HevcNvencEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy { public string InputFilePath { get; set; } = inputFilePath; public string OutputFilePath { get; set; } = outputFilePath; @@ -112,7 +112,7 @@ public class HEVCEncodingStrategy(string inputFilePath, string outputFilePath, E } [EncodingStrategy(EncoderType.H264)] -public class H264EncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy { +public class H264NvencEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy { public string InputFilePath { get; set; } = inputFilePath; public string OutputFilePath { get; set; } = outputFilePath; @@ -154,4 +154,135 @@ public class H264EncodingStrategy(string inputFilePath, string outputFilePath, E .CancellableThrough(cancellationToken) .ProcessAsynchronously(); } +} + +[EncodingStrategy(EncoderType.AV1)] +public class AV1SwEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy { + public string InputFilePath { get; set; } = inputFilePath; + public string OutputFilePath { get; set; } = outputFilePath; + + private static readonly Tuple[] QPTable = new Tuple[] { + new(1280*720, 64), + new(1920*1080, 96), + new(3840*2160, 128), + new(5760*2880, 96), //VR6K + new(8128*4096, 120) //VR8K + }.OrderBy(t => t.Item1).ToArray(); + + public async Task ExecuteAsync(CancellationToken cancellationToken, Action onProgress) { + var mediaInfo = await FFProbe.AnalyseAsync(InputFilePath, cancellationToken: cancellationToken); + if (mediaInfo.PrimaryVideoStream == null) return false; + int W = mediaInfo.PrimaryVideoStream.Width; + int H = mediaInfo.PrimaryVideoStream.Height; + int qp = Utils.ToQPValue(W, H, QPTable); + return await FFMpegArguments + .FromFileInput(InputFilePath, true, args => args.WithHardwareAcceleration()) + .OutputToFile(OutputFilePath, true, args => args + .CopyChannel(Channel.Audio) + .CopyChannel(Channel.Subtitle) + .WithVideoCodec(VideoCodec.LibaomAv1) + // TODO: adjust settings for software AV1 encoding + .WithFastStart() + ) + .NotifyOnProgress(progress => onProgress(new() { + progress = progress, + duration = mediaInfo.Duration, + framerate = (float)mediaInfo.PrimaryVideoStream.FrameRate, + job = job, + inputPath = InputFilePath, + outputPath = OutputFilePath + })) + .CancellableThrough(cancellationToken) + .ProcessAsynchronously(); + } +} + +[EncodingStrategy(EncoderType.HEVC)] +public class HevcSwEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy { + public string InputFilePath { get; set; } = inputFilePath; + public string OutputFilePath { get; set; } = outputFilePath; + + //TODO: needs to be adjusted for software HEVC encoding + private static readonly Tuple[] QPTable = new Tuple[] { + new(1280*720, 0), + new(1920*1080, 0), + new(3840*2160, 0), + new(5760*2880, 0), //VR6K + new(8128*4096, 0) //VR8K + }.OrderBy(t => t.Item1).ToArray(); + + public async Task ExecuteAsync(CancellationToken cancellationToken, Action onProgress) { + var mediaInfo = await FFProbe.AnalyseAsync(InputFilePath, cancellationToken: cancellationToken); + if (mediaInfo.PrimaryVideoStream == null) return false; + int W = mediaInfo.PrimaryVideoStream.Width; + int H = mediaInfo.PrimaryVideoStream.Height; + int qp = Utils.ToQPValue(W, H, QPTable); + return await FFMpegArguments + .FromFileInput(InputFilePath, true, args => args.WithHardwareAcceleration()) + .OutputToFile(OutputFilePath, true, args => args + .CopyChannel(Channel.Audio) + .CopyChannel(Channel.Subtitle) + .WithVideoCodec(VideoCodec.LibX265) + .WithArgument(new NvencSpeedPreset(NvencSpeed.p2)) + .WithArgument(new NvencTuneArgument(NvencTune.hq)) + .WithArgument(new NvencHighBitDepthArgument(true)) + .WithArgument(new NvencQPArgument((byte)qp)) + .WithFastStart() + ) + .NotifyOnProgress(progress => onProgress(new() { + progress = progress, + duration = mediaInfo.Duration, + framerate = (float)mediaInfo.PrimaryVideoStream.FrameRate, + job = job, + inputPath = InputFilePath, + outputPath = OutputFilePath + })) + .CancellableThrough(cancellationToken) + .ProcessAsynchronously(); + } +} + +[EncodingStrategy(EncoderType.H264)] +public class H264SwEncodingStrategy(string inputFilePath, string outputFilePath, EncodingJob job) : IEncodingStrategy { + public string InputFilePath { get; set; } = inputFilePath; + public string OutputFilePath { get; set; } = outputFilePath; + + //TODO: needs to be adjusted + private static readonly Tuple[] QPTable = new Tuple[] { + new(1280*720, 0), + new(1920*1080, 0), + new(3840*2160, 0), + new(5760*2880, 0), //VR6K + new(8128*4096, 0) //VR8K + }.OrderBy(t => t.Item1).ToArray(); + + public async Task ExecuteAsync(CancellationToken cancellationToken, Action onProgress) { + var mediaInfo = await FFProbe.AnalyseAsync(InputFilePath, cancellationToken: cancellationToken); + if (mediaInfo.PrimaryVideoStream == null) return false; + int W = mediaInfo.PrimaryVideoStream.Width; + int H = mediaInfo.PrimaryVideoStream.Height; + int qp = Utils.ToQPValue(W, H, QPTable); + return await FFMpegArguments + .FromFileInput(InputFilePath, true, args => args.WithHardwareAcceleration()) + .OutputToFile(OutputFilePath, true, args => args + .CopyChannel(Channel.Audio) + .CopyChannel(Channel.Subtitle) + .WithVideoCodec(VideoCodec.LibX264) + .WithArgument(new NvencSpeedPreset(NvencSpeed.p2)) + .WithArgument(new NvencTuneArgument(NvencTune.hq)) + .WithArgument(new NvencHighBitDepthArgument(true)) + .WithArgument(new NvencQPArgument((byte)qp)) + .WithFastStart() + ) + .NotifyOnProgress(progress => onProgress(new() { + progress = progress, + duration = mediaInfo.Duration, + framerate = (float)mediaInfo.PrimaryVideoStream.FrameRate, + job = job, + inputPath = InputFilePath, + outputPath = OutputFilePath + })) + .CancellableThrough(cancellationToken) + .ProcessAsynchronously(); + } } \ No newline at end of file