From 455436e7445fe632f3b9c8881a0765b22efb011c6631f3f2edfc6f99b7f58196 Mon Sep 17 00:00:00 2001 From: copecone Date: Tue, 13 Oct 2026 15:37:16 +0900 Subject: [PATCH] Fix Audio Cleanup On Render Abort --- Core/RenderManager.cs | 39 +++++++++++++++++++--------- Util/Audio/AudioSpectrumGenerator.cs | 1 + 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/Core/RenderManager.cs b/Core/RenderManager.cs index c53b3c1..da1900d 100644 --- a/Core/RenderManager.cs +++ b/Core/RenderManager.cs @@ -258,8 +258,10 @@ internal class RenderManager : MonoBehaviour if (stage != RenderStage.None) MarkFailed("the game exited or RenderManager was destroyed during rendering."); ResetRenderState(success: false); - + AudioSpectrumGenerator.Clear(); + // Cancels the render thread's audio waits, then releases the clip store (decoder thread, readers, chunks). + AudioMixer?.Dispose(); AudioMixer = null; ResetFrameHookState(); @@ -726,23 +728,35 @@ internal class RenderManager : MonoBehaviour endSuccessful = !IsRenderAborted; } + catch (OperationCanceledException) when (IsRenderAborted) + { + // Audio waits end with OperationCanceledException when the render is cancelled or has failed (OfflineAudioMixer.Cancel). + Melon.Logger.Msg("Render thread stopped: the render was aborted."); + DisposeEncoderAfterStop(videoFFmpeg); + } catch (Exception ex) { Melon.Logger.Error($"FFmpeg rendering thread error: {ex}"); MarkFailed("encoder thread error. See the log for details."); - try - { - videoFFmpeg.Dispose(); - } - catch (Exception disposeEx) - { - Melon.Logger.Error($"Encoder dispose after thread error also failed: {disposeEx.Message}"); - } - UnregisterEncoder(videoFFmpeg); + DisposeEncoderAfterStop(videoFFmpeg); } } + /// Releases the encoder after the render thread stopped early (abort or error) and removes it from the encoder list. + private void DisposeEncoderAfterStop(FFmpegNativeEncoder videoFFmpeg) + { + try + { + videoFFmpeg.Dispose(); + } + catch (Exception disposeEx) + { + Melon.Logger.Error($"Encoder dispose after the render thread stopped also failed: {disposeEx.Message}"); + } + UnregisterEncoder(videoFFmpeg); + } + /// /// Takes frames from ImageQueue in passCount order and sends each one to the encoder, followed by the audio /// samples of that frame. Waits on FrameAvailableEvent (50ms) while no frame is ready. @@ -873,12 +887,13 @@ internal class RenderManager : MonoBehaviour private void StopFFmpeg(bool success) { endRequested = true; - if (success) AudioMixer?.Complete(); - else AudioMixer?.Cancel(); if (!success) { MarkCancelled(); } + // The abort state is set first: the render thread reads it when its audio wait is cancelled. + if (success) AudioMixer?.Complete(); + else AudioMixer?.Cancel(); FrameAvailableEvent.Set(); skipPressToStart = false; _renderSetup = false; diff --git a/Util/Audio/AudioSpectrumGenerator.cs b/Util/Audio/AudioSpectrumGenerator.cs index d8567db..58d1c4c 100644 --- a/Util/Audio/AudioSpectrumGenerator.cs +++ b/Util/Audio/AudioSpectrumGenerator.cs @@ -171,6 +171,7 @@ internal static class AudioSpectrumGenerator if (!_view.IsValid || !_view.Contains(frame)) { source.Release(_view); + _view = default; _view = _store!.Acquire(source, frame); if (!_view.IsValid) return 0.0; }