SHA256
Fix FFmpegNativeEncoder to Work With QSV Properly
This commit is contained in:
1 parent
8dd33c1807
commit
32dccd4298
4 files changed
+893
-115
No files matched your search
@@ -6,6 +6,7 @@
|
||||
<NoWarn>MSB3277</NoWarn>
|
||||
<EnableSourceControlManagerQueries>false</EnableSourceControlManagerQueries>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+46
-55
@@ -111,8 +111,8 @@ namespace ADOFAIRenderer.Core
|
||||
public long wonFrame = -1;
|
||||
|
||||
// Private Instance Fields
|
||||
private VideoFFmpeg? _activeVideoFFmpeg;
|
||||
private readonly List<VideoFFmpeg> _allVideoFFmpegInstances = new List<VideoFFmpeg>();
|
||||
private FFmpegNativeEncoder? _activeVideoFFmpeg;
|
||||
private readonly List<FFmpegNativeEncoder> _allVideoFFmpegInstances = new List<FFmpegNativeEncoder>();
|
||||
private int _originalInputOffset;
|
||||
private GameObject? _debugMonitorObj;
|
||||
private GameObject? _escapeHandlerObj;
|
||||
@@ -136,8 +136,8 @@ namespace ADOFAIRenderer.Core
|
||||
|
||||
if (_activeVideoFFmpeg != null)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Msg("게임 종료 또는 RenderManager 파괴 감지: FFmpeg 렌더 프로세스를 강제 종료합니다.");
|
||||
_activeVideoFFmpeg.Kill();
|
||||
Melon<RenderMod>.Logger.Msg("게임 종료 또는 RenderManager 파괴 감지: FFmpeg 네이티브 인코더 리소스를 해제합니다.");
|
||||
_activeVideoFFmpeg.Dispose();
|
||||
_activeVideoFFmpeg = null;
|
||||
}
|
||||
|
||||
@@ -145,12 +145,12 @@ namespace ADOFAIRenderer.Core
|
||||
{
|
||||
if (_allVideoFFmpegInstances.Count > 0)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Msg($"게임 종료 감지: 생성된 모든 FFmpeg 프로세스({_allVideoFFmpegInstances.Count}개)를 강제 수거 및 종료합니다.");
|
||||
Melon<RenderMod>.Logger.Msg($"게임 종료 감지: 생성된 모든 FFmpeg 인코더 인스턴스({_allVideoFFmpegInstances.Count}개)를 수거 및 해제합니다.");
|
||||
foreach (var instance in _allVideoFFmpegInstances)
|
||||
{
|
||||
try
|
||||
{
|
||||
instance.Kill();
|
||||
instance.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -400,33 +400,8 @@ namespace ADOFAIRenderer.Core
|
||||
scrConductor.instance.song.time = -1000;
|
||||
}
|
||||
|
||||
private string FindFFmpegPath()
|
||||
{
|
||||
var isWindows = Application.platform == RuntimePlatform.WindowsPlayer;
|
||||
var ffmpegName = isWindows ? "ffmpeg.exe" : "ffmpeg";
|
||||
|
||||
// 1. Mods/ADOFAIRenderer/ffmpeg
|
||||
var path1 = Path.Combine(MelonEnvironment.ModsDirectory, "ADOFAIRenderer", ffmpegName);
|
||||
if (File.Exists(path1)) return path1;
|
||||
|
||||
// 2. Mods/ffmpeg
|
||||
var path2 = Path.Combine(MelonEnvironment.ModsDirectory, ffmpegName);
|
||||
if (File.Exists(path2)) return path2;
|
||||
|
||||
// 3. GameRootDirectory/ffmpeg
|
||||
var path3 = Path.Combine(MelonEnvironment.GameRootDirectory, ffmpegName);
|
||||
return File.Exists(path3) ? path3 : "";
|
||||
}
|
||||
|
||||
private void StartFFmpegThread()
|
||||
{
|
||||
var ffmpegPath = FindFFmpegPath();
|
||||
if (string.IsNullOrEmpty(ffmpegPath))
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("ffmpeg 바이너리를 찾을 수 없습니다. Mods/ADOFAIRenderer/ 에 ffmpeg를 넣어주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_debugMonitorObj)
|
||||
{
|
||||
_debugMonitorObj = new GameObject("DebugMonitorCanvas");
|
||||
@@ -456,18 +431,14 @@ namespace ADOFAIRenderer.Core
|
||||
var qp = Configuration.QP;
|
||||
var codecStr = Configuration.Codec;
|
||||
|
||||
var selectedCodec = VideoFFmpeg.Codec.H264_Default;
|
||||
if (Enum.TryParse<VideoFFmpeg.Codec>(codecStr, true, out var parsedCodec))
|
||||
var selectedCodec = Codec.H264;
|
||||
if (Enum.TryParse<Codec>(codecStr, true, out var parsedCodec))
|
||||
{
|
||||
selectedCodec = parsedCodec;
|
||||
}
|
||||
|
||||
var ext = ".mp4";
|
||||
if (selectedCodec == VideoFFmpeg.Codec.AV1_Default ||
|
||||
selectedCodec == VideoFFmpeg.Codec.AV1_NVIDIA ||
|
||||
selectedCodec == VideoFFmpeg.Codec.AV1_QSV ||
|
||||
selectedCodec == VideoFFmpeg.Codec.VP9_Default ||
|
||||
selectedCodec == VideoFFmpeg.Codec.VP9_NVIDIA)
|
||||
if (selectedCodec == Codec.AV1 || selectedCodec == Codec.VP9)
|
||||
{
|
||||
ext = ".webm";
|
||||
}
|
||||
@@ -478,11 +449,23 @@ namespace ADOFAIRenderer.Core
|
||||
|
||||
RenderThread = new Thread(() =>
|
||||
{
|
||||
VideoFFmpeg? videoFFmpeg = null;
|
||||
FFmpegNativeEncoder? videoFFmpeg = null;
|
||||
try
|
||||
{
|
||||
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
||||
videoFFmpeg = new VideoFFmpeg(Configuration.RenderWidth, Configuration.RenderHeight, effectiveVideoFps, videoFps, selectedCodec);
|
||||
var settings = new FFmpegNativeEncoder.EncoderSettings
|
||||
{
|
||||
Width = Configuration.RenderWidth,
|
||||
Height = Configuration.RenderHeight,
|
||||
FPS = effectiveVideoFps,
|
||||
Codec = selectedCodec,
|
||||
Mode = bitrateMode,
|
||||
QualityValue = qp,
|
||||
TargetBitrate = bitrate,
|
||||
MaxBitrate = maxBitrate
|
||||
};
|
||||
|
||||
videoFFmpeg = new FFmpegNativeEncoder(settings);
|
||||
|
||||
SetupCaptureDirectory();
|
||||
|
||||
@@ -491,7 +474,7 @@ namespace ADOFAIRenderer.Core
|
||||
{
|
||||
_allVideoFFmpegInstances.Add(videoFFmpeg);
|
||||
}
|
||||
videoFFmpeg.Start(_currentOutPath, bitrateMode, bitrate, maxBitrate, qp);
|
||||
videoFFmpeg.Start(_currentOutPath);
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -550,8 +533,15 @@ namespace ADOFAIRenderer.Core
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
|
||||
if (isRenderCancelled) videoFFmpeg.Kill();
|
||||
else videoFFmpeg.Close();
|
||||
if (isRenderCancelled)
|
||||
{
|
||||
videoFFmpeg.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
videoFFmpeg.End();
|
||||
videoFFmpeg.Dispose();
|
||||
}
|
||||
|
||||
lock (_allVideoFFmpegInstances)
|
||||
{
|
||||
@@ -564,7 +554,7 @@ namespace ADOFAIRenderer.Core
|
||||
catch (Exception ex)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error($"FFmpeg 렌더링 스레드 오류: {ex}");
|
||||
_activeVideoFFmpeg?.Kill();
|
||||
videoFFmpeg?.Dispose();
|
||||
if (videoFFmpeg != null)
|
||||
{
|
||||
lock (_allVideoFFmpegInstances)
|
||||
@@ -612,13 +602,20 @@ namespace ADOFAIRenderer.Core
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("렌더링이 실패했거나 취소되었습니다. 상태 및 리소스를 안전하게 청소합니다.");
|
||||
|
||||
// 1. 렌더링 백그라운드 스레드가 큐에 유입된 잔여 프레임 처리를 마무리하고 종료될 때까지 대기
|
||||
if (RenderThread is { IsAlive: true })
|
||||
{
|
||||
RenderThread.Join(2000);
|
||||
}
|
||||
|
||||
// 2. 스레드 종료 보장 후, 안전하게 FFmpeg 네이티브 인코더 리소스 해제
|
||||
if (_activeVideoFFmpeg != null)
|
||||
{
|
||||
_activeVideoFFmpeg.Kill(16384);
|
||||
_activeVideoFFmpeg.Dispose();
|
||||
_activeVideoFFmpeg = null;
|
||||
}
|
||||
|
||||
// 1. 메모리 누수 방지: 큐에 렌트된 임시 프레임 버퍼 풀 반환 및 클리어
|
||||
// 3. 메모리 누수 방지: 큐에 렌트된 임시 프레임 버퍼 풀 반환 및 클리어
|
||||
lock (ImageQueue)
|
||||
{
|
||||
foreach (var kvp in ImageQueue.Values)
|
||||
@@ -644,13 +641,7 @@ namespace ADOFAIRenderer.Core
|
||||
_debugMonitorObj = null;
|
||||
}
|
||||
|
||||
// 2. 렌더링 스레드가 큐 비어 있음을 감지하고 break 하도록 대기
|
||||
if (RenderThread is { IsAlive: true })
|
||||
{
|
||||
RenderThread.Join(2000);
|
||||
}
|
||||
|
||||
// 3. 인게임 카메라 및 렌더링용 RTCam 상태 안전 원복
|
||||
// 4. 인게임 카메라 및 렌더링용 RTCam 상태 안전 원복
|
||||
if (scrCamera.instance)
|
||||
{
|
||||
scrCamera.instance.forceRTCam = false;
|
||||
@@ -660,14 +651,14 @@ namespace ADOFAIRenderer.Core
|
||||
if (hook) Destroy(hook);
|
||||
}
|
||||
|
||||
// 4. 인게임 실패 방지 플래그 원상 복구
|
||||
// 5. 인게임 실패 방지 플래그 원상 복구
|
||||
if (scrController.instance)
|
||||
{
|
||||
scrController.instance.noFail = false;
|
||||
scrController.instance.noFailInfiniteMargin = false;
|
||||
}
|
||||
|
||||
// 5. 취소된 미완성 임시 동영상 파일 즉각 삭제
|
||||
// 6. 취소된 미완성 임시 동영상 파일 즉각 삭제
|
||||
if (!string.IsNullOrEmpty(_currentOutPath) && File.Exists(_currentOutPath))
|
||||
{
|
||||
try
|
||||
|
||||
+4
-4
@@ -177,18 +177,18 @@ namespace ADOFAIRenderer.UI
|
||||
|
||||
if (bitrateMode == RateControlMode.CBR || bitrateMode == RateControlMode.VBR)
|
||||
{
|
||||
if (!int.TryParse(_bitrateInput.text, out bitrate) || bitrate <= 0)
|
||||
if (!int.TryParse(_bitrateInput.text, out bitrate) || bitrate <= 0 || bitrate > 1_000_000)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Bitrate 값입니다. 0보다 큰 정수여야 합니다.");
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Bitrate 값입니다. 1에서 1,000,000 (kbps) 사이의 정수여야 합니다.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (bitrateMode == RateControlMode.VBR)
|
||||
{
|
||||
if (!int.TryParse(_maxBitrateInput.text, out maxBitrate) || maxBitrate <= 0)
|
||||
if (!int.TryParse(_maxBitrateInput.text, out maxBitrate) || maxBitrate <= 0 || maxBitrate > 1_000_000)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Max Bitrate 값입니다. 0보다 큰 정수여야 합니다.");
|
||||
Melon<RenderMod>.Logger.Error("올바르지 않은 Max Bitrate 값입니다. 1에서 1,000,000 (kbps) 사이의 정수여야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (maxBitrate < bitrate)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user