using Sdcb.FFmpeg.Codecs; using Sdcb.FFmpeg.Raw; using T3.Core.Logging; namespace T3.VideoServices; /// /// Picks this machine's best available H.264/HEVC hardware encoder by actually opening each /// candidate — being compiled into the build says nothing about the GPU/driver supporting it (e.g. a build /// ships h264_qsv/h264_amf 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 ffmpeg.exe (tier 2). /// 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" }; /// The working H.264 hardware encoder name (e.g. h264_nvenc), or null if none initializes. public static string? H264HardwareEncoder => _h264 ??= FindWorkingEncoder(H264Candidates); /// The working HEVC hardware encoder name, or null if none initializes. public static string? HevcHardwareEncoder => _hevc ??= FindWorkingEncoder(HevcCandidates); /// /// The encoder-input pixel format a given encoder accepts. Quick Sync (*_qsv) only takes nv12 /// (or a hardware qsv surface); NVENC and the software/AMF encoders accept planar yuv420p. /// Used by both the probe and the real encode so they agree — feeding QSV yuv420p fails to open. /// 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; }