diff --git a/Util/FFmpeg/FFmpegNativeEncoder.cs b/Util/FFmpeg/FFmpegNativeEncoder.cs index 0f87ef0..fea78c0 100644 --- a/Util/FFmpeg/FFmpegNativeEncoder.cs +++ b/Util/FFmpeg/FFmpegNativeEncoder.cs @@ -282,7 +282,7 @@ namespace ADOFAIRenderer.Util.FFmpeg } /// - /// 선택된 코덱에 대해 시도할 인코더 이름 목록을 우선순위 순으로 반환합니다. + /// 선택된 코덱에 대해 시도할 인코더 목록을 우선순위 순으로 반환합니다. /// /// 우선순위: 하드웨어 인코더(NVENC → AMF → QSV) → 소프트웨어 인코더(libx264 등) /// @@ -293,9 +293,9 @@ namespace ADOFAIRenderer.Util.FFmpeg /// /// Start()에서 이 목록을 순회하며 첫 번째로 구동 가능한 인코더를 사용합니다. /// - private string[] GetEncoderCandidates() + private VideoEncoder[] GetEncoderCandidates() { - return GPUUtil.GetPrioritizedEncoderCandidates(_settings.Codec, _settings.ForceSoftware).ToArray(); + return GPUUtil.GetPrioritizedEncoders(_settings.Codec, _settings.ForceSoftware).ToArray(); } /// @@ -366,12 +366,12 @@ namespace ADOFAIRenderer.Util.FFmpeg var candidates = GetEncoderCandidates(); var openedSuccessfully = false; - foreach (var encoderName in candidates) + foreach (var encoder in candidates) { // avcodec_find_encoder_by_name: 이름(예: "h264_nvenc")으로 인코더를 검색합니다. // 해당 인코더가 빌드에 포함되어 있지 않으면 null을 반환합니다. // 참고: https://ffmpeg.org/doxygen/trunk/group__lavc__encoding.html - var pCodec = ffmpeg.avcodec_find_encoder_by_name(encoderName); + var pCodec = ffmpeg.avcodec_find_encoder_by_name(encoder.Name); if (pCodec == null) continue; // avcodec_alloc_context3: 선택된 인코더용 코덱 컨텍스트를 할당합니다. @@ -402,17 +402,17 @@ namespace ADOFAIRenderer.Util.FFmpeg // // 참고: https://ffmpeg.org/doxygen/trunk/pixfmt_8h.html var selectedPixFmt = AVPixelFormat.AV_PIX_FMT_YUV420P; - if (encoderName.Contains("qsv")) + if (encoder.Kind == EncoderKind.QSV) { selectedPixFmt = AVPixelFormat.AV_PIX_FMT_NV12; _pCodecContext->pix_fmt = AVPixelFormat.AV_PIX_FMT_QSV; } - else if (encoderName.Contains("nvenc") || encoderName.Contains("amf")) + else if (encoder.Kind is EncoderKind.NVENC or EncoderKind.AMF) { selectedPixFmt = AVPixelFormat.AV_PIX_FMT_NV12; _pCodecContext->pix_fmt = selectedPixFmt; } - else if (encoderName == "libvvenc") + else if (encoder == VideoEncoder.LibVvenc) { // libvvenc: 10비트 YUV420P10LE만 지원 (이 빌드에서 yuv420p 미지원 확인됨) selectedPixFmt = AVPixelFormat.AV_PIX_FMT_YUV420P10LE; @@ -452,7 +452,7 @@ namespace ADOFAIRenderer.Util.FFmpeg // 키프레임은 완전한 이미지를 담고 있어 탐색(seek) 지점이 됩니다. // 너무 크면 탐색 정밀도가 떨어지고, 너무 작으면 파일 크기가 증가합니다. var calculatedGop = Math.Max(1, (int)Math.Round(_settings.FPS * _settings.GopSize)); - if (encoderName == "libvvenc") + if (encoder == VideoEncoder.LibVvenc) { // libvvenc (VVenC) requires IntraPeriod (gop_size) to be a multiple of its GOPSize (typically 32). _pCodecContext->gop_size = Math.Max(32, ((calculatedGop + 15) / 32) * 32); @@ -473,11 +473,11 @@ namespace ADOFAIRenderer.Util.FFmpeg // SVT-AV1 특수 사항: max_b_frames를 AVCodecContext에서 직접 설정하면 // "bad parameter" 에러가 발생하므로, svtav1-params를 통해 제어합니다. // pred-struct=1: Low Delay (IPPP) 구조로 B-프레임 비활성화 - if (encoderName == "libsvtav1") + if (encoder == VideoEncoder.LibSvtAv1) { ffmpeg.av_opt_set(_pCodecContext->priv_data, "svtav1-params", "pred-struct=1", 0); } - else if (encoderName == "libvvenc") + else if (encoder == VideoEncoder.LibVvenc) { // libvvenc relies on hierarchical GOP structures; forcing max_b_frames = 0 causes initialization failure. // We explicitly set max_b_frames to -1 to allow libvvenc to use its default GOP reference distance. @@ -500,7 +500,7 @@ namespace ADOFAIRenderer.Util.FFmpeg } // ── 품질/비트레이트 설정 적용 ── - ApplyQualitySettings(encoderName); + ApplyQualitySettings(encoder); // ── QSV 인코더 하드웨어 디바이스 초기화 ── // QSV(Quick Sync Video)는 Intel GPU의 고정 기능 인코딩 하드웨어를 사용합니다. @@ -519,7 +519,7 @@ namespace ADOFAIRenderer.Util.FFmpeg // hw_device_ctx: 시스템 메모리에서 인코더가 직접 프레임을 읽음 (호환성 우수) // // 참고: https://ffmpeg.org/doxygen/trunk/group__lavu__hwcontext.html - if (encoderName.Contains("qsv")) + if (encoder.Kind == EncoderKind.QSV) { AVBufferRef* hwDeviceCtx = null; var qsvReady = false; @@ -540,11 +540,11 @@ namespace ADOFAIRenderer.Util.FFmpeg if (err >= 0) { qsvReady = true; - Melon.Logger.Msg($"{encoderName}: child_device_type={childDeviceName} 방식으로 QSV 디바이스 생성 성공"); + Melon.Logger.Msg($"{encoder.Name}: child_device_type={childDeviceName} 방식으로 QSV 디바이스 생성 성공"); } else { - Melon.Logger.Warning($"{encoderName}: child_device_type 방식 실패 — {GetFFmpegErrorString(err)} (Code: {err})"); + Melon.Logger.Warning($"{encoder.Name}: child_device_type 방식 실패 — {GetFFmpegErrorString(err)} (Code: {err})"); } } @@ -561,7 +561,7 @@ namespace ADOFAIRenderer.Util.FFmpeg if (err >= 0) { qsvReady = true; - Melon.Logger.Msg($"{encoderName}: {childDeviceType} → QSV 파생 방식으로 디바이스 생성 성공"); + Melon.Logger.Msg($"{encoder.Name}: {childDeviceType} → QSV 파생 방식으로 디바이스 생성 성공"); } ffmpeg.av_buffer_unref(&childDeviceCtx); } @@ -576,7 +576,7 @@ namespace ADOFAIRenderer.Util.FFmpeg if (!qsvReady) { - Melon.Logger.Warning($"{encoderName}: 모든 QSV 디바이스 생성 전략 실패. 다음 후보 코덱을 테스트합니다."); + Melon.Logger.Warning($"{encoder.Name}: 모든 QSV 디바이스 생성 전략 실패. 다음 후보 코덱을 테스트합니다."); { var pCtx = _pCodecContext; ffmpeg.avcodec_free_context(&pCtx); @@ -618,7 +618,7 @@ namespace ADOFAIRenderer.Util.FFmpeg _pCodecContext->hw_frames_ctx = ffmpeg.av_buffer_ref(hwFramesRef); _isQsvEncoder = true; framesCtxReady = true; - Melon.Logger.Msg($"{encoderName}: hw_frames_ctx 초기화 성공 (GPU 프레임 풀 모드)"); + Melon.Logger.Msg($"{encoder.Name}: hw_frames_ctx 초기화 성공 (GPU 프레임 풀 모드)"); _pHwFrame = ffmpeg.av_frame_alloc(); if (_pHwFrame == null) @@ -626,7 +626,7 @@ namespace ADOFAIRenderer.Util.FFmpeg } else { - Melon.Logger.Warning($"{encoderName}: hw_frames_ctx 실패 — {GetFFmpegErrorString(err)} (Code: {err}). hw_device_ctx 모드로 폴백합니다."); + Melon.Logger.Warning($"{encoder.Name}: hw_frames_ctx 실패 — {GetFFmpegErrorString(err)} (Code: {err}). hw_device_ctx 모드로 폴백합니다."); } ffmpeg.av_buffer_unref(&hwFramesRef); } @@ -657,30 +657,30 @@ namespace ADOFAIRenderer.Util.FFmpeg // 느린 프리셋: 인코딩 느리지만 같은 품질에서 파일 크기 감소 // // 참고: https://ffmpeg.org/doxygen/trunk/group__opt__set__funcs.html - if (encoderName.Contains("nvenc")) + if (encoder.Kind == EncoderKind.NVENC) { // NVENC 프리셋 범위: p1(최고속) ~ p7(최고품질). p4 = 중간(default) ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "p4", 0); } - else if (encoderName.Contains("libx264") || encoderName.Contains("libx265")) + else if (encoder == VideoEncoder.Libx264 || encoder == VideoEncoder.Libx265) { // x264/x265 프리셋: ultrafast, superfast, veryfast, faster, fast, medium, slow, ... // CPU 인코딩은 병목이 되기 쉬우므로 빠른 프리셋 사용 ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "veryfast", 0); } - else if (encoderName == "libsvtav1") + else if (encoder == VideoEncoder.LibSvtAv1) { // SVT-AV1 프리셋: 0(최고 품질/최저 속도) ~ 13(최고 속도/최저 품질) ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "10", 0); } - else if (encoderName == "libaom-av1") + else if (encoder == VideoEncoder.LibAomAv1) { // libaom cpu-used: 0(최고 품질) ~ 8(최고 속도) ffmpeg.av_opt_set(_pCodecContext->priv_data, "cpu-used", "6", 0); // "realtime" usage: 단일 패스, 저지연 인코딩 모드 ffmpeg.av_opt_set(_pCodecContext->priv_data, "usage", "realtime", 0); } - else if (encoderName == "libvvenc") + else if (encoder == VideoEncoder.LibVvenc) { // libvvenc 프리셋: slower, slow, medium, fast, faster. 디폴트가 medium이므로 faster로 변경하여 성능 확보 ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "faster", 0); @@ -693,20 +693,20 @@ namespace ADOFAIRenderer.Util.FFmpeg // 성공하면 0 이상을 반환하고, 이후 avcodec_send_frame/avcodec_receive_packet으로 // 프레임 인코딩이 가능해집니다. // 참고: https://ffmpeg.org/doxygen/trunk/group__lavc__core.html - Melon.Logger.Msg($"[인코더 진단] {encoderName}: {_pCodecContext->width}x{_pCodecContext->height}, pix_fmt={_pCodecContext->pix_fmt}, gop={_pCodecContext->gop_size}, max_b_frames={_pCodecContext->max_b_frames}, framerate={_pCodecContext->framerate.num}/{_pCodecContext->framerate.den}, thread_count={_pCodecContext->thread_count}, bit_rate={_pCodecContext->bit_rate}, rc_max_rate={_pCodecContext->rc_max_rate}, rc_buffer_size={_pCodecContext->rc_buffer_size}, mode={_settings.Mode}"); + Melon.Logger.Msg($"[인코더 진단] {encoder.Name}: {_pCodecContext->width}x{_pCodecContext->height}, pix_fmt={_pCodecContext->pix_fmt}, gop={_pCodecContext->gop_size}, max_b_frames={_pCodecContext->max_b_frames}, framerate={_pCodecContext->framerate.num}/{_pCodecContext->framerate.den}, thread_count={_pCodecContext->thread_count}, bit_rate={_pCodecContext->bit_rate}, rc_max_rate={_pCodecContext->rc_max_rate}, rc_buffer_size={_pCodecContext->rc_buffer_size}, mode={_settings.Mode}"); var openResult = ffmpeg.avcodec_open2(_pCodecContext, pCodec, null); if (openResult >= 0) { - bool isHardware = encoderName.Contains("nvenc") || encoderName.Contains("amf") || encoderName.Contains("qsv"); - string accelStatus = isHardware ? "하드웨어 가속 활성화" : "소프트웨어 CPU 사용"; - Melon.Logger.Msg($"비디오 인코더 구동 성공: {encoderName} ({accelStatus})"); + var isHardware = encoder.IsHardware; + var accelStatus = isHardware ? "하드웨어 가속 활성화" : "소프트웨어 CPU 사용"; + Melon.Logger.Msg($"비디오 인코더 구동 성공: {encoder.Name} ({accelStatus})"); _dstPixFmt = selectedPixFmt; openedSuccessfully = true; break; } // 구동 실패 시 코덱 컨텍스트를 해제하고 다음 인코더를 시도합니다. - Melon.Logger.Warning($"{encoderName} 하드웨어 인코더 구동 실패: {GetFFmpegErrorString(openResult)} (Code: {openResult}). 다음 후보 코덱을 테스트합니다."); + Melon.Logger.Warning($"{encoder.Name} 하드웨어 인코더 구동 실패: {GetFFmpegErrorString(openResult)} (Code: {openResult}). 다음 후보 코덱을 테스트합니다."); var pCodecCtx = _pCodecContext; ffmpeg.avcodec_free_context(&pCodecCtx); _pCodecContext = null; @@ -832,7 +832,7 @@ namespace ADOFAIRenderer.Util.FFmpeg /// /// 참고: https://ffmpeg.org/doxygen/trunk/structAVCodecContext.html /// - private void ApplyQualitySettings(string encoderName) + private void ApplyQualitySettings(VideoEncoder encoder) { switch (_settings.Mode) { @@ -842,7 +842,7 @@ namespace ADOFAIRenderer.Util.FFmpeg // libvvenc는 rc_min_rate/rc_max_rate/rc_buffer_size를 지원하지 않으므로 // bit_rate만 설정하고 나머지는 내부 rate control에 위임합니다. - if (encoderName != "libvvenc") + if (encoder != VideoEncoder.LibVvenc) { // CBR에서는 목표/최대/최소 비트레이트를 모두 동일하게 설정합니다. // rc_buffer_size는 비트레이트 버퍼링의 크기를 결정합니다. @@ -851,7 +851,7 @@ namespace ADOFAIRenderer.Util.FFmpeg // nal-hrd=cbr: x264/x265 전용 옵션. NAL HRD(Hypothetical Reference Decoder) 규격에 // 맞는 CBR 비트스트림을 생성합니다. 스트리밍 호환성을 위해 필요할 수 있습니다. - if (encoderName.Contains("libx264") || encoderName.Contains("libx265")) + if (encoder == VideoEncoder.Libx264 || encoder == VideoEncoder.Libx265) { ffmpeg.av_opt_set(_pCodecContext->priv_data, "nal-hrd", "cbr", 0); } @@ -862,8 +862,8 @@ namespace ADOFAIRenderer.Util.FFmpeg // kbps → bps 변환 _pCodecContext->bit_rate = _settings.TargetBitrate * 1000L; - // libvvenc는 rc_max_rate/rc_buffer_size를 지원하지 않으므로 건너릴니다. - if (encoderName != "libvvenc") + // libvvenc는 rc_max_rate/rc_buffer_size를 지원하지 않으므로 건너뜁니다. + if (encoder != VideoEncoder.LibVvenc) { // MaxBitrate가 TargetBitrate보다 큰 경우 그대로 사용, // 그렇지 않으면 TargetBitrate의 1.5배를 최대값으로 설정합니다. @@ -882,24 +882,24 @@ namespace ADOFAIRenderer.Util.FFmpeg // 다른 코덱에서도 비슷한 수준의 품질을 제공합니다. var qpValue = _settings.QualityValue is >= 0 and <= 51 ? _settings.QualityValue : 18; - if (encoderName.Contains("nvenc")) + if (encoder.Kind == EncoderKind.NVENC) { // NVENC: "qp" 옵션 하나로 I/P/B 프레임 모두에 동일한 QP 적용 ffmpeg.av_opt_set(_pCodecContext->priv_data, "qp", qpValue.ToString(), 0); } - else if (encoderName.Contains("amf")) + else if (encoder.Kind == EncoderKind.AMF) { // AMF: I프레임(qp_i)과 P프레임(qp_p)의 QP를 별도로 설정해야 합니다. // B-프레임은 비활성화했으므로 qp_b는 설정하지 않습니다. ffmpeg.av_opt_set(_pCodecContext->priv_data, "qp_i", qpValue.ToString(), 0); ffmpeg.av_opt_set(_pCodecContext->priv_data, "qp_p", qpValue.ToString(), 0); } - else if (encoderName.Contains("qsv")) + else if (encoder.Kind == EncoderKind.QSV) { // QSV: "global_quality" 옵션으로 ICQ(Intelligent Constant Quality) 모드 사용 ffmpeg.av_opt_set(_pCodecContext->priv_data, "global_quality", qpValue.ToString(), 0); } - else if (encoderName == "libvvenc") + else if (encoder == VideoEncoder.LibVvenc) { // libvvenc: "qp" 옵션으로 Constant QP 모드 지정 ffmpeg.av_opt_set(_pCodecContext->priv_data, "qp", qpValue.ToString(), 0); diff --git a/Util/FFmpeg/VideoEncoder.cs b/Util/FFmpeg/VideoEncoder.cs new file mode 100644 index 0000000..0036113 --- /dev/null +++ b/Util/FFmpeg/VideoEncoder.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace ADOFAIRenderer.Util.FFmpeg +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum EncoderKind + { + Software, + NVENC, + AMF, + QSV, + VideoToolbox + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + public sealed class VideoEncoder : IEquatable + { + // ReSharper disable MemberCanBePrivate.Global + + // ── H.264 ── + public static readonly VideoEncoder H264_NVENC = new("h264_nvenc", Codec.H264, GPUVendor.NVIDIA, EncoderKind.NVENC); + public static readonly VideoEncoder H264_QSV = new("h264_qsv", Codec.H264, GPUVendor.Intel, EncoderKind.QSV); + public static readonly VideoEncoder H264_AMF = new("h264_amf", Codec.H264, GPUVendor.AMD, EncoderKind.AMF); + public static readonly VideoEncoder H264_VideoToolbox = new("h264_videotoolbox", Codec.H264, GPUVendor.Apple, EncoderKind.VideoToolbox); + public static readonly VideoEncoder Libx264 = new("libx264", Codec.H264, GPUVendor.Unknown, EncoderKind.Software); + + // ── H.265 (HEVC) ── + public static readonly VideoEncoder HEVC_NVENC = new("hevc_nvenc", Codec.H265, GPUVendor.NVIDIA, EncoderKind.NVENC); + public static readonly VideoEncoder HEVC_QSV = new("hevc_qsv", Codec.H265, GPUVendor.Intel, EncoderKind.QSV); + public static readonly VideoEncoder HEVC_AMF = new("hevc_amf", Codec.H265, GPUVendor.AMD, EncoderKind.AMF); + public static readonly VideoEncoder HEVC_VideoToolbox = new("hevc_videotoolbox", Codec.H265, GPUVendor.Apple, EncoderKind.VideoToolbox); + public static readonly VideoEncoder Libx265 = new("libx265", Codec.H265, GPUVendor.Unknown, EncoderKind.Software); + + // ── VP9 ── + public static readonly VideoEncoder VP9_NVENC = new("vp9_nvenc", Codec.VP9, GPUVendor.NVIDIA, EncoderKind.NVENC); + public static readonly VideoEncoder VP9_QSV = new("vp9_qsv", Codec.VP9, GPUVendor.Intel, EncoderKind.QSV); + public static readonly VideoEncoder LibvpxVP9 = new("libvpx-vp9", Codec.VP9, GPUVendor.Unknown, EncoderKind.Software); + + // ── AV1 ── + public static readonly VideoEncoder AV1_NVENC = new("av1_nvenc", Codec.AV1, GPUVendor.NVIDIA, EncoderKind.NVENC); + public static readonly VideoEncoder AV1_QSV = new("av1_qsv", Codec.AV1, GPUVendor.Intel, EncoderKind.QSV); + public static readonly VideoEncoder AV1_AMF = new("av1_amf", Codec.AV1, GPUVendor.AMD, EncoderKind.AMF); + public static readonly VideoEncoder LibSvtAv1 = new("libsvtav1", Codec.AV1, GPUVendor.Unknown, EncoderKind.Software); + public static readonly VideoEncoder LibAomAv1 = new("libaom-av1", Codec.AV1, GPUVendor.Unknown, EncoderKind.Software); + + // ── VVC ── + public static readonly VideoEncoder LibVvenc = new("libvvenc", Codec.VVC, GPUVendor.Unknown, EncoderKind.Software); + + // ReSharper restore MemberCanBePrivate.Global + + public static readonly IReadOnlyList All = new[] + { + H264_NVENC, H264_QSV, H264_AMF, H264_VideoToolbox, Libx264, + HEVC_NVENC, HEVC_QSV, HEVC_AMF, HEVC_VideoToolbox, Libx265, + VP9_NVENC, VP9_QSV, LibvpxVP9, + AV1_NVENC, AV1_QSV, AV1_AMF, LibSvtAv1, LibAomAv1, + LibVvenc + }; + + private static readonly Dictionary ByName = + All.ToDictionary(e => e.Name, StringComparer.OrdinalIgnoreCase); + + public string Name { get; } + public Codec Codec { get; } + public GPUVendor Vendor { get; } + public EncoderKind Kind { get; } + public bool IsHardware => Kind != EncoderKind.Software; + + private VideoEncoder(string name, Codec codec, GPUVendor vendor, EncoderKind kind) + { + Name = name; + Codec = codec; + Vendor = vendor; + Kind = kind; + } + + public static VideoEncoder? FromName(string name) + { + return string.IsNullOrEmpty(name) ? null : ByName.GetValueOrDefault(name); + } + + public bool Equals(VideoEncoder other) + { + if (ReferenceEquals(null, other)) return false; + return ReferenceEquals(this, other) || string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase); + } + + public override bool Equals(object? obj) + { + return ReferenceEquals(this, obj) || obj is VideoEncoder other && Equals(other); + } + + public override int GetHashCode() + { + return StringComparer.OrdinalIgnoreCase.GetHashCode(Name); + } + + public static bool operator ==(VideoEncoder left, VideoEncoder right) + { + return Equals(left, right); + } + + public static bool operator !=(VideoEncoder left, VideoEncoder right) + { + return !Equals(left, right); + } + + public override string ToString() => Name; + } +} diff --git a/Util/GPUUtil.cs b/Util/GPUUtil.cs index f7bc146..149c39a 100644 --- a/Util/GPUUtil.cs +++ b/Util/GPUUtil.cs @@ -25,15 +25,6 @@ namespace ADOFAIRenderer.Util // ReSharper disable once InconsistentNaming private const string AMDPattern = @"^AMD Radeon RX [0-9]+(?> XT)?$"; - - private static readonly Dictionary> CodecMap = new() - { - [Codec.H264] = new List<(GPUVendor, string)> { (GPUVendor.NVIDIA, "h264_nvenc"), (GPUVendor.Intel, "h264_qsv"), (GPUVendor.AMD, "h264_amf"), (GPUVendor.Apple, "h264_videotoolbox"), (GPUVendor.Unknown, "libx264") }, - [Codec.H265] = new List<(GPUVendor, string)> { (GPUVendor.NVIDIA, "hevc_nvenc"), (GPUVendor.Intel, "hevc_qsv"), (GPUVendor.AMD, "hevc_amf"), (GPUVendor.Apple, "hevc_videotoolbox"), (GPUVendor.Unknown, "libx265") }, - [Codec.VP9] = new List<(GPUVendor, string)> { (GPUVendor.NVIDIA, "vp9_nvenc"), (GPUVendor.Intel, "vp9_qsv"), (GPUVendor.Unknown, "libvpx-vp9") }, - [Codec.AV1] = new List<(GPUVendor, string)> { (GPUVendor.NVIDIA, "av1_nvenc"), (GPUVendor.Intel, "av1_qsv"), (GPUVendor.AMD, "av1_amf"), (GPUVendor.Unknown, "libsvtav1"), (GPUVendor.Unknown, "libaom-av1") }, - [Codec.VVC] = new List<(GPUVendor, string)> { (GPUVendor.Unknown, "libvvenc") } - }; private static GPUVendor _cachedVendor = GPUVendor.Unknown; private static bool _hasCachedVendor; @@ -137,24 +128,22 @@ namespace ADOFAIRenderer.Util return null; } - public static List GetPrioritizedEncoderCandidates(Codec codec, bool forceSoftware = false) + public static List GetPrioritizedEncoders(Codec codec, bool forceSoftware = false) { - if (!CodecMap.TryGetValue(codec, out var list)) return new List(); - - var prioritized = new List(); + var encoders = VideoEncoder.All.Where(e => e.Codec == codec).ToList(); if (forceSoftware) { - prioritized.AddRange(from item in list where item.Item1 == GPUVendor.Unknown select item.Item2); - return prioritized; + return encoders.Where(e => !e.IsHardware).ToList(); } var vendor = GetActiveGPUVendor(); - var others = new List(); + var prioritized = new List(); + var others = new List(); - foreach (var item in list) + foreach (var item in encoders) { - if (item.Item1 == vendor) prioritized.Add(item.Item2); - else others.Add(item.Item2); + if (item.Vendor == vendor) prioritized.Add(item); + else others.Add(item); } prioritized.AddRange(others);