Files
tooll3--t3/VideoServices/HardwareEncoderProbe.cs
2026-07-13 13:13:17 +08:00

85 lines
3.5 KiB
C#

using Sdcb.FFmpeg.Codecs;
using Sdcb.FFmpeg.Raw;
using T3.Core.Logging;
namespace T3.VideoServices;
/// <summary>
/// Picks this machine's best available H.264/HEVC <em>hardware</em> encoder by actually opening each
/// candidate — being compiled into the build says nothing about the GPU/driver supporting it (e.g. a build
/// ships <c>h264_qsv</c>/<c>h264_amf</c> wrappers, but they fail to init without an Intel/AMD device). Results
/// are cached. When none works, the caller falls back to an LGPL software codec (software H.264/HEVC is
/// libx264/libx265 = GPL, absent from the shipped build) or the user-supplied GPL <c>ffmpeg.exe</c> (tier 2).
/// </summary>
public static class HardwareEncoderProbe
{
// Vendor order: NVIDIA NVENC, Intel Quick Sync, AMD AMF. The first that opens wins.
private static readonly string[] H264Candidates = { "h264_nvenc", "h264_qsv", "h264_amf" };
private static readonly string[] HevcCandidates = { "hevc_nvenc", "hevc_qsv", "hevc_amf" };
/// <summary>The working H.264 hardware encoder name (e.g. <c>h264_nvenc</c>), or null if none initializes.</summary>
public static string? H264HardwareEncoder => _h264 ??= FindWorkingEncoder(H264Candidates);
/// <summary>The working HEVC hardware encoder name, or null if none initializes.</summary>
public static string? HevcHardwareEncoder => _hevc ??= FindWorkingEncoder(HevcCandidates);
/// <summary>
/// The encoder-input pixel format a given encoder accepts. Quick Sync (<c>*_qsv</c>) only takes <c>nv12</c>
/// (or a hardware <c>qsv</c> surface); NVENC and the software/AMF encoders accept planar <c>yuv420p</c>.
/// Used by both the probe and the real encode so they agree — feeding QSV <c>yuv420p</c> fails to open.
/// </summary>
public static AVPixelFormat EncoderInputFormat(string? encoderName)
=> encoderName != null && encoderName.Contains("_qsv") ? AVPixelFormat.Nv12 : AVPixelFormat.Yuv420p;
private static string? FindWorkingEncoder(string[] candidates)
{
if (!FfmpegLibrary.EnsureInitialized())
return null;
foreach (var name in candidates)
{
if (!CanOpenEncoder(name))
continue;
Log.Debug($"Hardware video encoder available: {name}");
return name;
}
return null;
}
// Opening with minimal CPU-input params is the only reliable availability test: hardware encoders accept
// system-memory frames (they upload internally), and a missing GPU/driver fails here at Open().
private static bool CanOpenEncoder(string encoderName)
{
CodecContext? context = null;
try
{
var codec = Codec.FindEncoderByName(encoderName);
context = new CodecContext(codec)
{
Width = 320,
Height = 240,
TimeBase = new AVRational(1, 30),
Framerate = new AVRational(30, 1),
PixelFormat = EncoderInputFormat(encoderName),
BitRate = 1_000_000,
};
context.Open();
return true;
}
catch (Exception e)
{
Log.Debug($"Hardware video encoder {encoderName} not usable: {e.Message}");
return false;
}
finally
{
context?.Dispose();
}
}
private static string? _h264;
private static string? _hevc;
}