Files
FileFlowsPlugins/VideoNodes/LogicalNodes/CanUseHardwareEncoding.cs

321 lines
13 KiB
C#

using FileFlows.VideoNodes.Helpers;
namespace FileFlows.VideoNodes;
/// <summary>
/// Node for checking if Flow Runner has access to hardware
/// </summary>
public class CanUseHardwareEncoding:Node
{
public override int Inputs => 1;
public override int Outputs => 2;
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/logical-nodes/can-use-hardware-encoding";
public override string Icon => "fas fa-eye";
public override FlowElementType Type => FlowElementType.Logic;
[Select(nameof(EncoderOptions), 1)]
public HardwareEncoder Encoder { get; set; }
private static List<ListOption> _EncoderOptions;
public static List<ListOption> EncoderOptions
{
get
{
if (_EncoderOptions == null)
{
_EncoderOptions = new List<ListOption>
{
new ListOption { Label = "NVIDIA", Value = "###GROUP###" },
new ListOption { Label = "NVIDIA H.264", Value = HardwareEncoder.Nvidia_H264 },
new ListOption { Label = "NVIDIA H.265", Value = HardwareEncoder.Nvidia_Hevc },
new ListOption { Label = "NVIDIA AV1", Value = HardwareEncoder.Nvidia_Hevc },
new ListOption { Label = "AMD", Value = "###GROUP###" },
new ListOption { Label = "AMD H.264", Value = HardwareEncoder.Amd_H264 },
new ListOption { Label = "AMD H.265", Value = HardwareEncoder.Amd_Hevc },
new ListOption { Label = "Intel QSV", Value = "###GROUP###" },
new ListOption { Label = "Intel QSV H.264", Value = HardwareEncoder.Qsv_H264 },
new ListOption { Label = "Intel QSV H.265", Value = HardwareEncoder.Qsv_Hevc },
new ListOption { Label = "VAAPI", Value = "###GROUP###" },
new ListOption { Label = "VAAPI H.264", Value = HardwareEncoder.Vaapi_H264 },
new ListOption { Label = "VAAPI H.265", Value = HardwareEncoder.Vaapi_Hevc },
new ListOption { Label = "VideoToolbox (MacOS)", Value = "###GROUP###" },
new ListOption { Label = "VideoToolbox H.264", Value = HardwareEncoder.VideoToolbox_H264},
new ListOption { Label = "VideoToolbox H.265", Value = HardwareEncoder.VideoToolbox_Hevc },
};
}
return _EncoderOptions;
}
}
public enum HardwareEncoder
{
Nvidia_H264 = 1,
Amd_H264 = 2,
Qsv_H264 = 3,
Vaapi_H264 = 4,
VideoToolbox_H264 = 5,
Nvidia_Hevc = 11,
Amd_Hevc = 12,
Qsv_Hevc = 13,
Vaapi_Hevc = 14,
VideoToolbox_Hevc = 15,
Nvidia_AV1 = 21,
Amd_AV1 = 22,
Qsv_AV1 = 23,
}
public override int Execute(NodeParameters args)
{
bool canProcess = false;
switch (Encoder)
{
case HardwareEncoder.Nvidia_H264: canProcess = CanProcess_Nvidia_H264(args); break;
case HardwareEncoder.Nvidia_Hevc: canProcess = CanProcess_Nvidia_Hevc(args); break;
case HardwareEncoder.Nvidia_AV1: canProcess = CanProcess_Nvidia_AV1(args); break;
case HardwareEncoder.Amd_H264: canProcess = CanProcess_Amd_H264(args); break;
case HardwareEncoder.Amd_Hevc: canProcess = CanProcess_Amd_Hevc(args); break;
case HardwareEncoder.Amd_AV1: canProcess = CanProcess_Amd_AV1(args); break;
case HardwareEncoder.Qsv_H264: canProcess = CanProcess_Qsv_H264(args); break;
case HardwareEncoder.Qsv_Hevc: canProcess = CanProcess_Qsv_Hevc(args); break;
case HardwareEncoder.Vaapi_H264: canProcess = CanProcess_Vaapi_H264(args); break;
case HardwareEncoder.Vaapi_Hevc: canProcess = CanProcess_Vaapi_Hevc(args); break;
case HardwareEncoder.VideoToolbox_H264: canProcess = CanProcess_VideoToolbox_H264(args); break;
case HardwareEncoder.VideoToolbox_Hevc: canProcess = CanProcess_VideoToolbox_Hevc(args); break;
}
return canProcess ? 1 : 2;
}
/// <summary>
/// Checks if this flow runner can use NVIDIA HEVC encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Nvidia_Hevc(NodeParameters args) => CanProcess(args, "hevc_nvenc");
/// <summary>
/// Checks if this flow runner can use NVIDIA AV1 encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Nvidia_AV1(NodeParameters args) => CanProcess(args, "av1_nvenc");
/// <summary>
/// Checks if this flow runner can use QSV AV1 encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Qsv_AV1(NodeParameters args) => CanProcess(args, "av1_qsv");
/// <summary>
/// Checks if this flow runner can use NVIDIA H.264 encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Nvidia_H264(NodeParameters args) => CanProcess(args, "h264_nvenc");
/// <summary>
/// Checks if this flow runner can use VideoToolbox HEVC encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_VideoToolbox_Hevc(NodeParameters args) => CanProcess(args, "hevc_videotoolbox");
/// <summary>
/// Checks if this flow runner can use VideoToolbox H.264 encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_VideoToolbox_H264(NodeParameters args) => CanProcess(args, "h264_videotoolbox");
/// <summary>
/// Checks if this flow runner can use AND AV1 encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Amd_AV1(NodeParameters args) => CanProcess(args, "hevc_av1");
/// <summary>
/// Checks if this flow runner can use AND HEVC encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Amd_Hevc(NodeParameters args) => CanProcess(args, "hevc_amf");
/// <summary>
/// Checks if this flow runner can use AND H.264 encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Amd_H264(NodeParameters args) => CanProcess(args, "h264_amf");
/// <summary>
/// Checks if this flow runner can use Intels QSV HEVC encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Qsv_Hevc(NodeParameters args) => CanProcess(args, "hevc_qsv", "-global_quality", "28", "-load_plugin", "hevc_hw");
/// <summary>
/// Checks if this flow runner can use Intels QSV H.264 encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Qsv_H264(NodeParameters args) => CanProcess(args, "h264_qsv");
/// <summary>
/// Checks if this flow runner can use VAAPI HEVC encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Vaapi_Hevc(NodeParameters args) => CanProcess(args, "hevc_vaapi");
/// <summary>
/// Checks if this flow runner can use VAAPI H.264 encoder
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>true if can use it, otherwise false</returns>
internal static bool CanProcess_Vaapi_H264(NodeParameters args) => CanProcess(args, "h264_vaapi");
/// <summary>
/// Gets if a encoder/decoder has been disabled by a variable
/// </summary>
/// <param name="args">the node parameters</param>
/// <param name="parameters">the parameters to check</param>
/// <returns>if a encoder/decoder has been disabled by a variable</returns>
internal static bool DisabledByVariables(NodeParameters args, string[] parameters)
{
if (parameters.Any(x => x.ToLower().Contains("nvenc")))
{
if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "nonvidia").Value as bool? == true)
return true;
}
else if (parameters.Any(x => x.ToLower().Contains("qsv")))
{
if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "noqsv").Value as bool? == true)
return true;
}
else if (parameters.Any(x => x.ToLower().Contains("vaapi")))
{
if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "novaapi").Value as bool? == true)
return true;
}
else if (parameters.Any(x => x.ToLower().Contains("amf")))
{
if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "noamf" || x.Key.ToLowerInvariant() == "noamd").Value as bool? == true)
return true;
}
else if (parameters.Any(x => x.ToLower().Contains("videotoolbox")))
{
if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "novideotoolbox").Value as bool? == true)
return true;
}
return false;
}
private static bool CanProcess(NodeParameters args, params string[] encodingParams)
{
if (encodingParams.Any(x => x.Contains("vaapi") && OperatingSystem.IsWindows()))
return false; // dont bother checking vaapi on windows, unlikely the user has added support for it
if (DisabledByVariables(args, encodingParams))
return false;
string ffmpeg = args.GetToolPath("FFmpeg");
if (string.IsNullOrEmpty(ffmpeg))
{
args.Logger.ELog("FFmpeg tool not found.");
return false;
}
return CanProcess(args, ffmpeg, encodingParams);
}
/// <summary>
/// Tests if the encoding parameters can be executed
/// </summary>
/// <param name="args">the node paramterse</param>
/// <param name="ffmpeg">the location of ffmpeg</param>
/// <param name="encodingParams">the encoding parameter to test</param>
/// <returns>true if can be processed</returns>
internal static bool CanProcess(NodeParameters args, string ffmpeg, string[] encodingParams)
{
bool can = CanExecute();
if (can == false && encodingParams?.Contains("amf") == true)
{
// AMD/AMF has a issue where it reports false at first but then passes
// https://github.com/revenz/FileFlows/issues/106
Thread.Sleep(2000);
can = CanExecute();
}
return can;
bool CanExecute()
{
bool vaapi = encodingParams.Any(x => x.Contains("vaapi")) && VaapiHelper.VaapiLinux;
List<string> arguments = encodingParams.ToList();
if (vaapi)
arguments.AddRange(new [] { "-vf", "format=nv12,hwupload", "-strict", "-2"});
arguments.InsertRange(0, new []
{
"-loglevel",
"error",
"-f",
"lavfi",
"-i",
"color=black:s=1080x1080",
"-vframes",
"1",
"-an",
"-c:v"
});
if (vaapi)
{
arguments.InsertRange(0,
new[] { "-fflags", "+genpts", "-vaapi_device", VaapiHelper.VaapiRenderDevice });
arguments.Add(FileHelper.Combine(args.TempPath, Guid.NewGuid() + ".mkv"));
}
else
{
arguments.AddRange(new []
{
"-f",
"null",
"-"
});
}
var cmd = args.Process.ExecuteShellCommand(new ExecuteArgs
{
Command = ffmpeg,
ArgumentList = arguments.ToArray(),
Silent = true
}).Result;
string output = cmd.Output?.Contains("va_openDriver() returns 0") == true ? null : cmd.Output;
if (cmd.ExitCode != 0 || string.IsNullOrWhiteSpace(output) == false)
{
string asStr = string.Join(" ", arguments.Select(x => x.Contains(" ") ? "\"" + x + "\"" : x));
args.Logger?.WLog($"Cant process '{ffmpeg} {asStr}': {cmd.Output ?? ""}");
return false;
}
return true;
}
}
}