properly implemented svtav1 strategy and added a temporary set of values, same with x265 (cpu)

This commit is contained in:
Samuele Lorefice
2025-12-23 03:58:10 +01:00
parent 2ddcfd2edb
commit f6869e54ac
3 changed files with 182 additions and 47 deletions

View File

@@ -162,11 +162,11 @@ public class AV1SwEncodingStrategy(string inputFilePath, string outputFilePath,
public string OutputFilePath { get; set; } = outputFilePath; public string OutputFilePath { get; set; } = outputFilePath;
private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] { private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] {
new(1280*720, 64), new(1280*720, 27),
new(1920*1080, 96), new(1920*1080, 28),
new(3840*2160, 128), new(3840*2160, 29),
new(5760*2880, 96), //VR6K new(5760*2880, 30), //VR6K
new(8128*4096, 120) //VR8K new(8128*4096, 32) //VR8K
}.OrderBy(t => t.Item1).ToArray(); }.OrderBy(t => t.Item1).ToArray();
public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) { public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) {
@@ -180,8 +180,11 @@ public class AV1SwEncodingStrategy(string inputFilePath, string outputFilePath,
.OutputToFile(OutputFilePath, true, args => args .OutputToFile(OutputFilePath, true, args => args
.CopyChannel(Channel.Audio) .CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle) .CopyChannel(Channel.Subtitle)
.WithVideoCodec(VideoCodec.LibaomAv1) .WithVideoCodec("libsvtav1")
// TODO: adjust settings for software AV1 encoding .WithCustomArgument($"-preset 7")
.WithConstantRateFactor(qp)
.WithCustomArgument("-svtav1-params tune=0")
.WithVideoFilters(filterOptions => {if (W > 0 && H > 0) filterOptions.Scale(W, H);})
.WithFastStart() .WithFastStart()
) )
.NotifyOnProgress(progress => onProgress(new() { .NotifyOnProgress(progress => onProgress(new() {
@@ -204,11 +207,11 @@ public class HevcSwEncodingStrategy(string inputFilePath, string outputFilePath,
//TODO: needs to be adjusted for software HEVC encoding //TODO: needs to be adjusted for software HEVC encoding
private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] { private static readonly Tuple<int, int>[] QPTable = new Tuple<int, int>[] {
new(1280*720, 0), new(1280*720, 20),
new(1920*1080, 0), new(1920*1080, 22),
new(3840*2160, 0), new(3840*2160, 26),
new(5760*2880, 0), //VR6K new(5760*2880, 26), //VR6K
new(8128*4096, 0) //VR8K new(8128*4096, 28) //VR8K
}.OrderBy(t => t.Item1).ToArray(); }.OrderBy(t => t.Item1).ToArray();
public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) { public async Task<bool> ExecuteAsync(CancellationToken cancellationToken, Action<EncodingProgress> onProgress) {
@@ -223,10 +226,10 @@ public class HevcSwEncodingStrategy(string inputFilePath, string outputFilePath,
.CopyChannel(Channel.Audio) .CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle) .CopyChannel(Channel.Subtitle)
.WithVideoCodec(VideoCodec.LibX265) .WithVideoCodec(VideoCodec.LibX265)
.WithArgument(new NvencSpeedPreset(NvencSpeed.p2)) .WithSpeedPreset(Speed.Slow)
.WithArgument(new NvencTuneArgument(NvencTune.hq)) .WithConstantRateFactor(qp)
.WithArgument(new NvencHighBitDepthArgument(true)) .WithCustomArgument("-x265-params profile=main10:high-tier=1:aq-mode=3:psy-rd=1:rc-lookahead=60:bframes=6")
.WithArgument(new NvencQPArgument((byte)qp)) .WithVideoFilters(filterOptions => {if (W > 0 && H > 0) filterOptions.Scale(W, H);})
.WithFastStart() .WithFastStart()
) )
.NotifyOnProgress(progress => onProgress(new() { .NotifyOnProgress(progress => onProgress(new() {

View File

@@ -29,7 +29,7 @@ class NvencSpeedPreset(NvencSpeed speed) : FFMpegCore.Arguments.IArgument {
} }
class NvencTuneArgument(NvencTune tune) : FFMpegCore.Arguments.IArgument { class NvencTuneArgument(NvencTune tune) : FFMpegCore.Arguments.IArgument {
public string Text { get { return $"-tune {tune.ToString().ToLower()}"; } } public string Text { get { return $"-tune {(int)tune}"; } }
} }
class NvencHighBitDepthArgument(bool enable) : FFMpegCore.Arguments.IArgument { class NvencHighBitDepthArgument(bool enable) : FFMpegCore.Arguments.IArgument {

View File

@@ -8,7 +8,8 @@ string tempPath = Path.Combine(Environment.CurrentDirectory, "tmp");
string outputDir = Path.Combine(Environment.CurrentDirectory, "output"); string outputDir = Path.Combine(Environment.CurrentDirectory, "output");
Directory.CreateDirectory(outputDir); Directory.CreateDirectory(outputDir);
Directory.CreateDirectory(tempPath); Directory.CreateDirectory(tempPath);
const string inputFile = "testVid8k.mp4"; const string inputFile = "testVid.mp4";
const int MAX_JOBS = 3;
GlobalFFOptions.Configure(options => { GlobalFFOptions.Configure(options => {
options.BinaryFolder = ffmpegPath; options.BinaryFolder = ffmpegPath;
@@ -22,44 +23,175 @@ if (mediaInfo.PrimaryVideoStream == null) {
Console.WriteLine("No video stream found."); Console.WriteLine("No video stream found.");
return; return;
} }
var W = mediaInfo.PrimaryVideoStream.Width; var W = mediaInfo.PrimaryVideoStream.Width / 2;
var H = mediaInfo.PrimaryVideoStream.Height; var H = mediaInfo.PrimaryVideoStream.Height / 2;
for (int qp = 32; qp <= 160; qp += 32) { Queue<Task> GpuEncodingTasks = new();
await AV1Encode(W, H, qp); List<Task> CpuTasks = new();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
for (int cq = 24; cq <= 34; cq += 2) {
GpuEncodingTasks.Enqueue(Encode(Encoder.AV1_NVENC, 1920, 1080, cq, cancellationToken));
GpuEncodingTasks.Enqueue(Encode(Encoder.HEVC_NVENC, 1920, 1080, cq, cancellationToken));
GpuEncodingTasks.Enqueue(Encode(Encoder.H264_NVENC, 1920, 1080, cq, cancellationToken));
CpuTasks.Add(Encode(Encoder.AV1_CPU, 1920, 1080, cq, cancellationToken));
CpuTasks.Add(Encode(Encoder.HEVC_CPU, 1920, 1080, cq, cancellationToken));
CpuTasks.Add(Encode(Encoder.H264_CPU, 1920, 1080, cq, cancellationToken));
}
//Run all GPU tasks sequentially
while (GpuEncodingTasks.Count > 0) {
var task = GpuEncodingTasks.Dequeue();
try {
task.RunSynchronously();
} catch (Exception ex) {
Console.WriteLine($"Error during GPU encoding: {ex.Message}");
}
} }
/* //Run all CPU tasks in parallel
for (int qp = 32; qp <= 250; qp += 32) { try {
await AV1Encode(1920, 1080, qp); while (CpuTasks.Count > 0) {
}*/ var runningTasks = CpuTasks.Take(MAX_JOBS).ToList();
CpuTasks = CpuTasks.Skip(MAX_JOBS).ToList();
await Task.WhenAll(runningTasks);
}
await Task.WhenAll(CpuTasks);
}
catch (Exception ex) {
Console.WriteLine($"Error during CPU encoding: {ex.Message}");
}
cancellationTokenSource.Cancel();
return;
Task Encode(Encoder encoder, int W = -1, int H = -1, int CQ = 23, CancellationToken cancellationToken = default) {
var outputFile = Path.Combine(outputDir, $"output_{encoder}-{W}x{H}_cq{CQ}.mp4");
if (File.Exists(outputFile) && new FileInfo(outputFile).Length > 0) {
Console.WriteLine($"Skipping existing file {outputFile}");
return Task.CompletedTask;
}
FFMpegArgumentProcessor ffmpegArgs = encoder switch {
Encoder.AV1_NVENC => AV1Encode( outputFile, W, H, CQ),
Encoder.HEVC_NVENC => HEVCEncode( outputFile, W, H, CQ),
Encoder.H264_NVENC => H264Encode( outputFile, W, H, CQ),
Encoder.AV1_CPU => AV1EncodeCPU( outputFile, W, H, CQ),
Encoder.HEVC_CPU => HEVCEncodeCPU( outputFile, W, H, CQ),
Encoder.H264_CPU => H264EncodeCPU( outputFile, W, H, CQ),
_ => throw new ArgumentOutOfRangeException(nameof(encoder), encoder, null)
};
ffmpegArgs.WithLogLevel(FFMpegLogLevel.Info)
.NotifyOnProgress(progress => Console.WriteLine($"Encoding {outputFile}: {progress:g}/{mediaInfo.Duration:g} {progress / mediaInfo.Duration:P}"))
.CancellableThrough(cancellationToken);
Console.WriteLine(ffmpegArgs.Arguments);
return new Task<bool>(_ => {
Console.WriteLine("Starting encoding: " + outputFile);
return ffmpegArgs.ProcessAsynchronously().Result;
}, cancellationToken);
}
Task AV1Encode(int W = -1, int H = -1, int QP = 23) { // AV1 is visually lossless at QP 23 FFMpegArgumentProcessor AV1Encode(string filePath, int W = -1, int H = -1, int CQ = 23) { // AV1 is visually lossless at CQ 23
var outputFile = Path.Combine(outputDir, $"output_av1-{W}x{H}_qp{QP}.mp4"); return FFMpegArguments
var ffmpegArgs = FFMpegArguments .FromFileInput(inputFile, false, options => options.WithHardwareAcceleration())
.FromFileInput(inputFile, true, options => options .OutputToFile(filePath, true, options => options
.WithHardwareAcceleration() .CopyChannel(Channel.Audio)
) .CopyChannel(Channel.Subtitle)
.OutputToFile(outputFile, true, options => options .WithVideoCodec("av1_nvenc")
.WithArgument(new NvencSpeedPreset(NvencSpeed.p7))
.WithArgument(new NvencTuneArgument(NvencTune.hq))
.WithArgument(new NvencHighBitDepthArgument(true))
.WithCustomArgument("-rc vbr")
.WithCustomArgument($"-cq {CQ}")
.WithVideoFilters(filterOptions => { if (W > 0 && H > 0) filterOptions.Scale(W, H); })
.WithFastStart()
);
}
FFMpegArgumentProcessor HEVCEncode(string filePath, int W = -1, int H = -1, int CQ = 23) {
return FFMpegArguments
.FromFileInput(inputFile, true, options => options.WithHardwareAcceleration())
.OutputToFile(filePath, true, options => options
.CopyChannel(Channel.Audio) .CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle) .CopyChannel(Channel.Subtitle)
.WithVideoCodec("hevc_nvenc") .WithVideoCodec("hevc_nvenc")
.WithArgument(new NvencSpeedPreset(NvencSpeed.p4)) .WithArgument(new NvencSpeedPreset(NvencSpeed.p7))
.WithArgument(new NvencTuneArgument(NvencTune.hq)) .WithArgument(new NvencTuneArgument(NvencTune.hq))
.WithArgument(new NvencHighBitDepthArgument(true)) .WithCustomArgument("-rc vbr_hq")
.WithArgument(new NvencQPArgument((byte)QP)) .WithCustomArgument($"-cq {(byte)CQ}")
.WithVideoFilters(filterOptions => { .WithVideoFilters(filterOptions => { if (W > 0 && H > 0) filterOptions.Scale(W, H); })
if (W > 0 && H > 0) filterOptions.Scale(W, H);
})
.WithFastStart() .WithFastStart()
) );
.WithLogLevel(FFMpegLogLevel.Info)
.NotifyOnProgress(progress => Console.WriteLine($"Encoding {outputFile}: {progress:g}/{mediaInfo.Duration:g} {progress/mediaInfo.Duration:P}"));
Console.WriteLine(ffmpegArgs.Arguments);
var res = ffmpegArgs.ProcessSynchronously();
return Task.CompletedTask;
} }
FFMpegArgumentProcessor H264Encode(string filePath, int W = -1, int H = -1, int CQ = 23) {
return FFMpegArguments
.FromFileInput(inputFile, true, options => options.WithHardwareAcceleration())
.OutputToFile(filePath, true, options => options
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec("h264_nvenc")
.WithArgument(new NvencSpeedPreset(NvencSpeed.p7))
.WithArgument(new NvencTuneArgument(NvencTune.hq))
.WithCustomArgument("-rc vbr_hq")
.WithCustomArgument($"-cq {(byte)CQ}") // approximate CRF to CQ
.WithVideoFilters(filterOptions => { if (W > 0 && H > 0) filterOptions.Scale(W, H); })
.WithFastStart()
);
}
FFMpegArgumentProcessor AV1EncodeCPU(string filePath, int W = -1, int H = -1, int CQ = 23) {
return FFMpegArguments
.FromFileInput(inputFile)
.OutputToFile(filePath, true, options => options
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec("libsvtav1")
.WithCustomArgument($"-preset 3")
.WithConstantRateFactor(CQ)
.WithCustomArgument("-svtav1-params tune=0")
.WithVideoFilters(filterOptions => {if (W > 0 && H > 0) filterOptions.Scale(W, H);})
.WithFastStart()
);
}
FFMpegArgumentProcessor HEVCEncodeCPU(string filePath, int W = -1, int H = -1, int CQ = 23) {
return FFMpegArguments
.FromFileInput(inputFile)
.OutputToFile(filePath, true, options => options
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec(VideoCodec.LibX265)
.WithSpeedPreset(Speed.Slow)
.WithConstantRateFactor(CQ)
.WithCustomArgument("-x265-params profile=main10:high-tier=1:aq-mode=3:psy-rd=1:rc-lookahead=60:bframes=6")
.WithVideoFilters(filterOptions => {if (W > 0 && H > 0) filterOptions.Scale(W, H);})
.WithFastStart()
);
}
FFMpegArgumentProcessor H264EncodeCPU(string filePath, int W = -1, int H = -1, int CQ = 23) {
return FFMpegArguments
.FromFileInput(inputFile)
.OutputToFile(filePath, true, options => options
.CopyChannel(Channel.Audio)
.CopyChannel(Channel.Subtitle)
.WithVideoCodec(VideoCodec.LibX264)
.WithSpeedPreset(Speed.Slow)
.WithConstantRateFactor(CQ)
.WithCustomArgument("-x265-params profile=main10:high-tier=1:aq-mode=3:psy-rd=1:rc-lookahead=60:bframes=6")
.WithVideoFilters(filterOptions => {if (W > 0 && H > 0) filterOptions.Scale(W, H);})
.WithFastStart()
);
}
public enum Encoder {
AV1_NVENC,
HEVC_NVENC,
H264_NVENC,
AV1_CPU,
HEVC_CPU,
H264_CPU
}