using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Paca.Core.Media; using Paca.Core.Platform; using Xunit; namespace Paca.Tests.Core; public class GpuHardwareAccelerationProfilerTests { private sealed class MockProbeProcessRunner : IProcessRunner { private readonly Func _handler; public MockProbeProcessRunner(Func handler) { _handler = handler; } public Task RunAsync(ProcessSpec spec, Action? onLine, CancellationToken ct) { return Task.FromResult(_handler(spec)); } } private const string FullRtx4090EncodersOutput = "Encoders:\n" + " V..... h264_nvenc NVIDIA NVENC H.264 encoder (codec h264)\n" + " V..... hevc_nvenc NVIDIA NVENC hevc encoder (codec hevc)\n" + " V..... av1_nvenc NVIDIA NVENC av1 encoder (codec av1)\n" + " V..... libx264 libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (codec h264)\n" + " V..... libx265 libx265 H.265 / HEVC (codec hevc)\n" + " V..... libsvtav1 SVT-AV1(Scalable Video Technology for AV1) encoder (codec av1)\n"; private const string IntelArcEncodersOutput = "Encoders:\n" + " V..... h264_qsv H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (Intel Quick Sync Video acceleration) (codec h264)\n" + " V..... hevc_qsv HEVC (Intel Quick Sync Video acceleration) (codec hevc)\n" + " V..... av1_qsv AV1 (Intel Quick Sync Video acceleration) (codec av1)\n" + " V..... libx264 libx264 H.264 (codec h264)\n"; private const string AmdRadeon7000EncodersOutput = "Encoders:\n" + " V..... h264_amf AMD AMF H.264 Encoder (codec h264)\n" + " V..... hevc_amf AMD AMF HEVC Encoder (codec hevc)\n" + " V..... av1_amf AMD AMF AV1 Encoder (codec av1)\n" + " V..... libx264 libx264 H.264 (codec h264)\n"; [Fact] public void GpuCapabilities_NVENC_H264_HEVC_AV1_전부_파싱_검증() { var caps = GpuHardwareAccelerationProfiler.ParseCapabilities(FullRtx4090EncodersOutput); Assert.True(caps.Supports(GpuEncoderBackend.NvidiaNvenc, VideoCodecTarget.H264)); Assert.True(caps.Supports(GpuEncoderBackend.NvidiaNvenc, VideoCodecTarget.HEVC_H265)); Assert.True(caps.Supports(GpuEncoderBackend.NvidiaNvenc, VideoCodecTarget.AV1)); Assert.False(caps.Supports(GpuEncoderBackend.IntelQsv, VideoCodecTarget.AV1)); } [Fact] public void GpuCapabilities_IntelQSV_AMD_AMF_AV1_파싱_검증() { var intelCaps = GpuHardwareAccelerationProfiler.ParseCapabilities(IntelArcEncodersOutput); Assert.True(intelCaps.Supports(GpuEncoderBackend.IntelQsv, VideoCodecTarget.AV1)); Assert.True(intelCaps.Supports(GpuEncoderBackend.IntelQsv, VideoCodecTarget.HEVC_H265)); Assert.False(intelCaps.Supports(GpuEncoderBackend.NvidiaNvenc, VideoCodecTarget.AV1)); var amdCaps = GpuHardwareAccelerationProfiler.ParseCapabilities(AmdRadeon7000EncodersOutput); Assert.True(amdCaps.Supports(GpuEncoderBackend.AmdAmf, VideoCodecTarget.AV1)); Assert.True(amdCaps.Supports(GpuEncoderBackend.AmdAmf, VideoCodecTarget.HEVC_H265)); Assert.False(amdCaps.Supports(GpuEncoderBackend.IntelQsv, VideoCodecTarget.AV1)); } [Fact] public void GpuCapabilities_최적_인코더_자동선택_GPU_우선_및_CPU_폴백() { var rtxCaps = GpuHardwareAccelerationProfiler.ParseCapabilities(FullRtx4090EncodersOutput); Assert.Equal("av1_nvenc", rtxCaps.GetOptimalEncoder(VideoCodecTarget.AV1)); Assert.Equal("hevc_nvenc", rtxCaps.GetOptimalEncoder(VideoCodecTarget.HEVC_H265)); Assert.Equal("h264_nvenc", rtxCaps.GetOptimalEncoder(VideoCodecTarget.H264)); var cpuOnlyCaps = GpuHardwareAccelerationProfiler.ParseCapabilities( "Encoders:\n V..... libx264\n V..... libx265\n V..... libsvtav1\n"); Assert.Equal("libsvtav1", cpuOnlyCaps.GetOptimalEncoder(VideoCodecTarget.AV1)); Assert.Equal("libx265", cpuOnlyCaps.GetOptimalEncoder(VideoCodecTarget.HEVC_H265)); Assert.Equal("libx264", cpuOnlyCaps.GetOptimalEncoder(VideoCodecTarget.H264)); } [Fact] public async Task GpuProfiler_실제_런타임_프로빙_미지원시_폴백_검증() { var runner = new MockProbeProcessRunner(spec => { if (spec.Args.Contains("-encoders")) { return new ProcessResult(0, FullRtx4090EncodersOutput, ""); } if (spec.Args.Contains("av1_nvenc")) { return new ProcessResult(1, "", "[av1_nvenc @ 000001] Driver does not support the required nvenc features\nError initializing output stream"); } if (spec.Args.Contains("hevc_nvenc") || spec.Args.Contains("h264_nvenc")) { return new ProcessResult(0, "frame= 1 fps=0.0 q=0.0 Lsize=N/A video:0KiB", ""); } return new ProcessResult(0, "", ""); }); var profiler = new GpuHardwareAccelerationProfiler(runner); var report = await profiler.ProfileAllCodecsAsync("ffmpeg.exe", CancellationToken.None); Assert.True(report.IsProbeVerified); Assert.Equal("libsvtav1", report.GetBestEncoder(VideoCodecTarget.AV1)); Assert.Equal("hevc_nvenc", report.GetBestEncoder(VideoCodecTarget.HEVC_H265)); Assert.Equal("h264_nvenc", report.GetBestEncoder(VideoCodecTarget.H264)); } [Fact] public void GpuProfiler_인코더별_최적_인수_생성_검증() { var nvencAv1Args = GpuHardwareAccelerationProfiler.BuildEncodingArgs("av1_nvenc", crfOrQuality: 24, preset: "p4"); Assert.Contains("-c:v", nvencAv1Args); Assert.Contains("av1_nvenc", nvencAv1Args); Assert.Contains("-cq", nvencAv1Args); Assert.Contains("24", nvencAv1Args); Assert.Contains("-preset", nvencAv1Args); Assert.Contains("p4", nvencAv1Args); var qsvAv1Args = GpuHardwareAccelerationProfiler.BuildEncodingArgs("av1_qsv", crfOrQuality: 26); Assert.Contains("av1_qsv", qsvAv1Args); Assert.Contains("-global_quality", qsvAv1Args); Assert.Contains("26", qsvAv1Args); var svtAv1Args = GpuHardwareAccelerationProfiler.BuildEncodingArgs("libsvtav1", crfOrQuality: 28, preset: "6"); Assert.Contains("libsvtav1", svtAv1Args); Assert.Contains("-crf", svtAv1Args); Assert.Contains("28", svtAv1Args); } [Fact] public void HardwareEncoderDetector_기존_하위호환_및_AV1_확장_검증() { var sample = FullRtx4090EncodersOutput; var caps = HardwareEncoderDetector.ParseCapabilities(sample); Assert.True(caps.HasNvidiaNvenc); Assert.True(caps.HasNvencAv1); Assert.True(caps.HasNvencHevc); var runner = new MockProbeProcessRunner(_ => new ProcessResult(0, sample, "")); var detector = new HardwareEncoderDetector(runner); Assert.Equal("av1_nvenc", detector.GetBestAv1Encoder(caps)); Assert.Equal("hevc_nvenc", detector.GetBestHevcEncoder(caps)); Assert.Equal("h264_nvenc", detector.GetBestH264Encoder(caps)); } [Fact] public void HardwareTranscoderStudio_Auto_Backend와_Capabilities_결합시_GPU_인코더_자동선택() { var caps = GpuHardwareAccelerationProfiler.ParseCapabilities(FullRtx4090EncodersOutput); var spec = new TranscodePresetSpec { Backend = HardwareEncoderBackend.Auto, Codec = VideoCodecTarget.AV1 }; var args = HardwareTranscoderStudio.BuildFfmpegTranscodeCommand("in.mp4", "out.mp4", spec, caps); Assert.Contains("av1_nvenc", args); } }