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 GpuEncodingTasks = new(); List 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(_ => { 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 }