diff --git a/Core/RenderManager.cs b/Core/RenderManager.cs index b277c59..e185660 100644 --- a/Core/RenderManager.cs +++ b/Core/RenderManager.cs @@ -387,9 +387,9 @@ namespace ADOFAIRenderer.Core private IEnumerator SetupPlay() { - if (Configuration.RenderAudio && GetOutputExtension(Configuration.VideoConfig!.Codec) != ".mp4") + if (Configuration.AudioConfig.RenderAudio && GetOutputExtension(Configuration.VideoConfig!.Codec) != ".mp4") { - Configuration.AudioSampleRate = 48000; + Configuration.AudioConfig.AudioSampleRate = 48000; } for (; countdown > 0; countdown--) @@ -435,13 +435,13 @@ namespace ADOFAIRenderer.Core AudioSpectrumGenerator.Initialize(scrConductor.instance.song.clip); } - if (Configuration.RenderAudio) + if (Configuration.AudioConfig.RenderAudio) { - AudioMixer = new OfflineAudioMixer(Configuration.AudioChannels, Configuration.AudioSampleRate); + AudioMixer = new OfflineAudioMixer(Configuration.AudioConfig.AudioChannels, Configuration.AudioConfig.AudioSampleRate); AudioMixer.InitializeBGM(scrConductor.instance.song.clip); var effectiveVideoFps = Math.Min(Configuration.VideoFPS, Configuration.RenderFPS); - SyncCalculator = new AudioSyncCalculator(Configuration.AudioSampleRate, effectiveVideoFps); + SyncCalculator = new AudioSyncCalculator(Configuration.AudioConfig.AudioSampleRate, effectiveVideoFps); } scrConductor.instance.song.clip = null; @@ -499,13 +499,13 @@ namespace ADOFAIRenderer.Core MaxBitrate = Configuration.VideoConfig.MaxBitrate, GopSize = Configuration.VideoConfig.KeyframeInterval, ForceSoftware = Configuration.VideoConfig.ForceSoftwareEncoder, - Audio = Configuration.RenderAudio ? new FFmpegNativeEncoder.AudioSettings + Audio = Configuration.AudioConfig.RenderAudio ? new FFmpegNativeEncoder.AudioSettings { - SampleRate = Configuration.AudioSampleRate, - Channels = Configuration.AudioChannels, + SampleRate = Configuration.AudioConfig.AudioSampleRate, + Channels = Configuration.AudioConfig.AudioChannels, InputSampleFormat = AVSampleFormat.AV_SAMPLE_FMT_FLT, AudioCodecId = codecId, - Bitrate = Configuration.AudioBitrate + Bitrate = Configuration.AudioConfig.AudioBitrate } : null }; @@ -586,13 +586,13 @@ namespace ADOFAIRenderer.Core } } - if (Configuration.RenderAudio) + if (Configuration.AudioConfig.RenderAudio) { var frameIdx = (int)(passCount - 1); var effectiveVideoFps = Math.Min(Configuration.VideoFPS, Configuration.RenderFPS); var frameTime = frameIdx / effectiveVideoFps; - var channels = Configuration.AudioChannels; + var channels = Configuration.AudioConfig.AudioChannels; var samplesNeeded = SyncCalculator.GetNextFrameSamples(); var audioBuffer = ArrayPool.Shared.Rent(samplesNeeded * channels); diff --git a/Data/PresetManager.cs b/Data/PresetManager.cs index 2013c72..20a89ea 100644 --- a/Data/PresetManager.cs +++ b/Data/PresetManager.cs @@ -84,9 +84,9 @@ namespace ADOFAIRenderer.Data // 2. legacy top-level Codec -> VideoConfig.Codec var legacyCodecToken = configObj["Codec"]; - if (legacyCodecToken != null) + if (legacyCodecToken != null && legacyCodecToken.Type != JTokenType.Null) { - if (configObj["VideoConfig"] == null) + if (configObj["VideoConfig"] == null || configObj["VideoConfig"]!.Type == JTokenType.Null) { configObj["VideoConfig"] = new JObject(); } @@ -98,22 +98,27 @@ namespace ADOFAIRenderer.Data } // 3. legacy top-level Audio properties -> AudioConfig - if (configObj["AudioConfig"] == null) + string[] legacyAudioProps = { "RenderAudio", "AudioSampleRate", "AudioChannels", "AudioBitrate" }; + foreach (var prop in legacyAudioProps) { - var audioConfigObj = new JObject(); - if (configObj["RenderAudio"] != null) - audioConfigObj["RenderAudio"] = configObj["RenderAudio"]; - if (configObj["AudioSampleRate"] != null) - audioConfigObj["AudioSampleRate"] = configObj["AudioSampleRate"]; - if (configObj["AudioChannels"] != null) - audioConfigObj["AudioChannels"] = configObj["AudioChannels"]; - if (configObj["AudioBitrate"] != null) - audioConfigObj["AudioBitrate"] = configObj["AudioBitrate"]; - - if (audioConfigObj.HasValues) + var legacyToken = configObj[prop]; + if (legacyToken != null && legacyToken.Type != JTokenType.Null) { - configObj["AudioConfig"] = audioConfigObj; + if (configObj["AudioConfig"] == null || configObj["AudioConfig"]!.Type == JTokenType.Null) + { + configObj["AudioConfig"] = new JObject(); + } + + var audioConfigObj = (JObject)configObj["AudioConfig"]!; + audioConfigObj[prop] ??= legacyToken; } + + configObj.Remove(prop); + } + + if (configObj["AudioConfig"] != null && configObj["AudioConfig"]!.Type == JTokenType.Null) + { + configObj.Remove("AudioConfig"); } } diff --git a/Data/RenderConfig.cs b/Data/RenderConfig.cs index 6a4cba0..bd68dc4 100644 --- a/Data/RenderConfig.cs +++ b/Data/RenderConfig.cs @@ -10,59 +10,53 @@ namespace ADOFAIRenderer.Data public int RenderWidth { get; set; } public int RenderHeight { get; set; } - public VideoEncodingConfig? VideoConfig { get; set; } - public AudioEncodingConfig? AudioConfig { get; set; } + public VideoEncodingConfig VideoConfig { get; set; } = new VideoEncodingConfig(); + public AudioEncodingConfig AudioConfig { get; set; } = new AudioEncodingConfig(); public double DelayAfterLevelEnd { get; set; } public bool ShowJudgementText { get; set; } public int? RandomSeed { get; set; } = null; public bool ProcessAudio { get; set; } - - // 하위 호환 및 편의를 위한 접근자 - public bool RenderAudio - { - get => AudioConfig?.RenderAudio ?? true; - set => (AudioConfig ??= new AudioEncodingConfig()).RenderAudio = value; - } - - public int AudioSampleRate - { - get => AudioConfig?.AudioSampleRate ?? 44100; - set { (AudioConfig ??= new AudioEncodingConfig()).AudioSampleRate = value; } - } - - public int AudioChannels - { - get => AudioConfig?.AudioChannels ?? 2; - set { (AudioConfig ??= new AudioEncodingConfig()).AudioChannels = value; } - } - - public int AudioBitrate - { - get => AudioConfig?.AudioBitrate ?? 256000; - set { (AudioConfig ??= new AudioEncodingConfig()).AudioBitrate = value; } - } } public class VideoEncodingConfig { public Codec Codec { get; set; } = Codec.H264; - public RateControlMode RateControlMode { get; set; } + public RateControlMode RateControlMode { get; set; } = RateControlMode.CBR; - public int Bitrate { get; set; } - public int MaxBitrate { get; set; } + public int Bitrate { get; set; } = 30000; + public int MaxBitrate { get; set; } = 50000; // ReSharper disable once InconsistentNaming - public int QP { get; set; } - public double KeyframeInterval { get; set; } - public bool ForceSoftwareEncoder { get; set; } + public int QP { get; set; } = 18; + public double KeyframeInterval { get; set; } = 2.0; + public bool ForceSoftwareEncoder { get; set; } = false; } public class AudioEncodingConfig { + private int _audioSampleRate = 44100; + private int _audioChannels = 2; + private int _audioBitrate = 256000; + public bool RenderAudio { get; set; } = true; - public int AudioSampleRate { get; set; } = 44100; - public int AudioChannels { get; set; } = 2; - public int AudioBitrate { get; set; } = 256000; + + public int AudioSampleRate + { + get => _audioSampleRate <= 0 ? 44100 : _audioSampleRate; + set => _audioSampleRate = value <= 0 ? 44100 : value; + } + + public int AudioChannels + { + get => _audioChannels <= 0 ? 2 : _audioChannels; + set => _audioChannels = value <= 0 ? 2 : value; + } + + public int AudioBitrate + { + get => _audioBitrate <= 0 ? 256000 : _audioBitrate; + set => _audioBitrate = value <= 0 ? 256000 : value; + } } } \ No newline at end of file diff --git a/Patch/Engine/AudioPatches.cs b/Patch/Engine/AudioPatches.cs index 599864a..7c49406 100644 --- a/Patch/Engine/AudioPatches.cs +++ b/Patch/Engine/AudioPatches.cs @@ -12,7 +12,7 @@ namespace ADOFAIRenderer.Patch.Engine [HarmonyPrefix] public static bool Prefix(string snd, double time, AudioMixerGroup group, float volume, int priority) { - if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.RenderAudio) + if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio) { // Cache the clip on the main thread RenderManager.Instance.AudioMixer?.CacheClip(snd); @@ -23,7 +23,7 @@ namespace ADOFAIRenderer.Patch.Engine [HarmonyPostfix] public static void Postfix(AudioSource __result, string snd, double time, AudioMixerGroup group, float volume, int priority) { - if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.RenderAudio && __result != null) + if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio && __result != null) { // Convert absolute DSP time to relative time in seconds from render start double relativeStartTime = time - RenderManager.Instance.dspTimeBase; @@ -40,7 +40,7 @@ namespace ADOFAIRenderer.Patch.Engine [HarmonyPrefix] public static bool Prefix(string snd, double time, double endTime, float volume, int priority) { - if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.RenderAudio) + if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio) { var mixer = RenderManager.Instance.AudioMixer; if (mixer != null) @@ -67,7 +67,7 @@ namespace ADOFAIRenderer.Patch.Engine // ReSharper disable once InconsistentNaming public static void Postfix(AudioSource __instance, float value) { - if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.RenderAudio) + if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio) { var mixer = RenderManager.Instance.AudioMixer; if (mixer != null && mixer.TryGetSoundEvent(__instance, out var soundEvent)) @@ -86,7 +86,7 @@ namespace ADOFAIRenderer.Patch.Engine [HarmonyPostfix] public static void Postfix(AudioSource __instance, double time) { - if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.RenderAudio) + if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio) { var mixer = RenderManager.Instance.AudioMixer; if (mixer != null && mixer.TryGetSoundEvent(__instance, out var soundEvent)) @@ -103,7 +103,7 @@ namespace ADOFAIRenderer.Patch.Engine [HarmonyPostfix] public static void Postfix(AudioSource __instance) { - if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.RenderAudio) + if (RenderManager.IsCapturingActive && RenderManager.Instance.Configuration.AudioConfig.RenderAudio) { RenderManager.Instance.AudioMixer?.RemoveSourceMapping(__instance); } diff --git a/UI/RenderConfigUIManager.cs b/UI/RenderConfigUIManager.cs index ca7cc2f..0bfa8f7 100644 --- a/UI/RenderConfigUIManager.cs +++ b/UI/RenderConfigUIManager.cs @@ -327,7 +327,7 @@ namespace ADOFAIRenderer.UI AudioBitrate = config.AudioConfig.AudioBitrate }; - if (_root.Contains(_audioEncodingConfigContainer)) + if (_audioEncodingConfigContainer != null && _root.Contains(_audioEncodingConfigContainer)) { _root.Remove(_audioEncodingConfigContainer); } @@ -372,9 +372,14 @@ namespace ADOFAIRenderer.UI // 3. 샘플 레이트 로우 var sampleRateRow = CreateRow(); var sampleRateLabel = CreateFormLabel("샘플 레이트(Hz)"); + var sampleRateStr = tempAudioConfig.AudioSampleRate.ToString(); + if (sampleRateStr != "44100" && sampleRateStr != "48000") + { + sampleRateStr = "44100"; + } var sampleRateDropdown = new DropdownField( new List { "44100", "48000" }, - tempAudioConfig.AudioSampleRate.ToString() + sampleRateStr ); sampleRateRow.Add(sampleRateLabel); sampleRateRow.Add(sampleRateDropdown); @@ -498,6 +503,7 @@ namespace ADOFAIRenderer.UI config.RandomSeed = null; RenderManager.Instance.Configuration.VideoConfig ??= GenerateEncodingConfig(); + RenderManager.Instance.Configuration.AudioConfig ??= GenerateAudioEncodingConfig(); RenderManager.Instance.SetupCaptureDirectory(); RenderManager.Instance.SetupGameScene(); @@ -569,6 +575,9 @@ namespace ADOFAIRenderer.UI } } + config.VideoConfig ??= GenerateEncodingConfig(); + config.AudioConfig ??= GenerateAudioEncodingConfig(); + RenderManager.Instance.Configuration = config; } @@ -921,6 +930,7 @@ namespace ADOFAIRenderer.UI } currentConfig.VideoConfig = RenderManager.Instance.Configuration.VideoConfig ?? GenerateEncodingConfig(); + currentConfig.AudioConfig = RenderManager.Instance.Configuration.AudioConfig ?? GenerateAudioEncodingConfig(); PresetManager.SavePreset(presetName, currentConfig); nameField.value = string.Empty;