197 lines
8.0 KiB
C#
197 lines
8.0 KiB
C#
using System;
|
|
using EncodingSampleTest;
|
|
using FFMpegCore;
|
|
using FFMpegCore.Enums;
|
|
|
|
string ffmpegPath = Path.Combine(Environment.CurrentDirectory, "vendor");
|
|
string tempPath = Path.Combine(Environment.CurrentDirectory, "tmp");
|
|
string outputDir = Path.Combine(Environment.CurrentDirectory, "output");
|
|
Directory.CreateDirectory(outputDir);
|
|
Directory.CreateDirectory(tempPath);
|
|
const string inputFile = "testVid.mp4";
|
|
const int MAX_JOBS = 3;
|
|
|
|
GlobalFFOptions.Configure(options => {
|
|
options.BinaryFolder = ffmpegPath;
|
|
options.TemporaryFilesFolder = tempPath;
|
|
});
|
|
|
|
if(!File.Exists(inputFile)) return;
|
|
|
|
var mediaInfo = FFProbe.Analyse(inputFile);
|
|
if (mediaInfo.PrimaryVideoStream == null) {
|
|
Console.WriteLine("No video stream found.");
|
|
return;
|
|
}
|
|
var W = mediaInfo.PrimaryVideoStream.Width / 2;
|
|
var H = mediaInfo.PrimaryVideoStream.Height / 2;
|
|
|
|
Queue<Task> GpuEncodingTasks = new();
|
|
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
|
|
try {
|
|
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);
|
|
}
|
|
|
|
FFMpegArgumentProcessor AV1Encode(string filePath, int W = -1, int H = -1, int CQ = 23) { // AV1 is visually lossless at CQ 23
|
|
return FFMpegArguments
|
|
.FromFileInput(inputFile, false, options => options.WithHardwareAcceleration())
|
|
.OutputToFile(filePath, true, options => options
|
|
.CopyChannel(Channel.Audio)
|
|
.CopyChannel(Channel.Subtitle)
|
|
.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.Subtitle)
|
|
.WithVideoCodec("hevc_nvenc")
|
|
.WithArgument(new NvencSpeedPreset(NvencSpeed.p7))
|
|
.WithArgument(new NvencTuneArgument(NvencTune.hq))
|
|
.WithCustomArgument("-rc vbr_hq")
|
|
.WithCustomArgument($"-cq {(byte)CQ}")
|
|
.WithVideoFilters(filterOptions => { if (W > 0 && H > 0) filterOptions.Scale(W, H); })
|
|
.WithFastStart()
|
|
);
|
|
}
|
|
|
|
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
|
|
} |