From 968512aa863a8a1a7eac41f4c8cd1a575c5ba764 Mon Sep 17 00:00:00 2001 From: GoaLitiuM Date: Fri, 7 Oct 2016 02:36:45 +0300 Subject: [PATCH] Add configurable audio resample quality option, defaults to High Previous default resample quality is the same as the new Medium setting. Low is the fastest setting and matches in the quality with some other BMS players out there. High setting should be quite good while Highest being the slowest and not much better than the High setting. --- Pulsus/Audio/AudioEngine.cs | 8 ++++++-- Pulsus/FFmpeg/FFmpegContext.cs | 22 +++++++++++++++++++++- Pulsus/FFmpeg/FFmpegHelper.cs | 4 ++-- Pulsus/Game.cs | 4 +++- Pulsus/Pulsus.csproj | 1 + Pulsus/Shared/ResampleQuality.cs | 10 ++++++++++ Pulsus/Shared/Settings.cs | 8 ++++---- Pulsus/Shared/SettingsParser.cs | 23 ++++++++++++++++++++++- 8 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 Pulsus/Shared/ResampleQuality.cs diff --git a/Pulsus/Audio/AudioEngine.cs b/Pulsus/Audio/AudioEngine.cs index cbcc665..6918a72 100644 --- a/Pulsus/Audio/AudioEngine.cs +++ b/Pulsus/Audio/AudioEngine.cs @@ -51,8 +51,12 @@ public SoundInstanceInternal(SoundInstance instance) long buffersMixedCount = 0; float volume = 1.0f; - public AudioEngine(string audioDevice = null, AudioDriver driver = AudioDriver.Default, int sampleRate = 44100, int bufferLength = 1024) + private ResampleQuality resampleQuality; + + public AudioEngine(string audioDevice = null, AudioDriver driver = AudioDriver.Default, int sampleRate = 44100, int bufferLength = 1024, ResampleQuality resampleQuality = ResampleQuality.High) { + this.resampleQuality = resampleQuality; + SDL.SDL_InitSubSystem(SDL.SDL_INIT_AUDIO); audioCallback = AudioCallback; @@ -261,7 +265,7 @@ public uint GetBufferOffset() public SoundData LoadFromFile(string path) { return new SoundData(FFmpeg.FFmpegHelper.SoundFromFileResample(path, - audioSpec.freq, audioSpec.channels, audioSpec.format)); + audioSpec.freq, audioSpec.channels, audioSpec.format, resampleQuality)); } public void Play(SoundInstance soundInstance, int polyphony) diff --git a/Pulsus/FFmpeg/FFmpegContext.cs b/Pulsus/FFmpeg/FFmpegContext.cs index 926e308..dd3eae1 100644 --- a/Pulsus/FFmpeg/FFmpegContext.cs +++ b/Pulsus/FFmpeg/FFmpegContext.cs @@ -296,7 +296,7 @@ private void SetupCodecContext(AVStream* stream) format = (int)codecContext->sample_fmt; } - public void ConvertToFormat(AVSampleFormat sampleFormat, int sampleRate, int channels) + public void ConvertToFormat(AVSampleFormat sampleFormat, int sampleRate, int channels, ResampleQuality resampleQuality = ResampleQuality.High) { if (format == (int)sampleFormat && this.sampleRate == sampleRate && @@ -312,6 +312,7 @@ public void ConvertToFormat(AVSampleFormat sampleFormat, int sampleRate, int cha int channelLayout = (int)ffmpeg.av_get_default_channel_layout(channels); swrContext = ffmpeg.swr_alloc(); + ffmpeg.av_opt_set_int(swrContext, "in_channel_layout", (int)codecContext->channel_layout, 0); ffmpeg.av_opt_set_int(swrContext, "out_channel_layout", channelLayout, 0); ffmpeg.av_opt_set_int(swrContext, "in_channel_count", codecContext->channels, 0); @@ -321,6 +322,25 @@ public void ConvertToFormat(AVSampleFormat sampleFormat, int sampleRate, int cha ffmpeg.av_opt_set_sample_fmt(swrContext, "in_sample_fmt", codecContext->sample_fmt, 0); ffmpeg.av_opt_set_sample_fmt(swrContext, "out_sample_fmt", sampleFormat, 0); + switch (resampleQuality) + { + case ResampleQuality.Low: + ffmpeg.av_opt_set_int(swrContext, "filter_size", 0, 0); + ffmpeg.av_opt_set_int(swrContext, "phase_shift", 0, 0); + break; + case ResampleQuality.Medium: + // default ffmpeg settings + break; + case ResampleQuality.High: + ffmpeg.av_opt_set_int(swrContext, "filter_size", 128, 0); + ffmpeg.av_opt_set_double(swrContext, "cutoff", 1.0, 0); + break; + case ResampleQuality.Highest: + ffmpeg.av_opt_set_int(swrContext, "filter_size", 256, 0); + ffmpeg.av_opt_set_double(swrContext, "cutoff", 1.0, 0); + break; + } + if (ffmpeg.swr_init(swrContext) != 0) throw new ApplicationException("Failed init SwrContext: " + FFmpegHelper.logLastLine); } diff --git a/Pulsus/FFmpeg/FFmpegHelper.cs b/Pulsus/FFmpeg/FFmpegHelper.cs index 62d1616..6fe8c72 100644 --- a/Pulsus/FFmpeg/FFmpegHelper.cs +++ b/Pulsus/FFmpeg/FFmpegHelper.cs @@ -134,7 +134,7 @@ public static byte[] SoundFromFile(string path, out int sampleRate, out int chan } } - public static byte[] SoundFromFileResample(string path, int sampleRate, int channels, ushort sampleFormatSDL) + public static byte[] SoundFromFileResample(string path, int sampleRate, int channels, ushort sampleFormatSDL, ResampleQuality resampleQuality = ResampleQuality.High) { AVSampleFormat targetFormat2; switch (sampleFormatSDL) @@ -157,7 +157,7 @@ public static byte[] SoundFromFileResample(string path, int sampleRate, int chan ffContext.SelectStream(AVMediaType.AVMEDIA_TYPE_AUDIO); // setup resamplers and other format converters if needed - ffContext.ConvertToFormat(targetFormat2, sampleRate, channels); + ffContext.ConvertToFormat(targetFormat2, sampleRate, channels, resampleQuality); // FFmpeg only approximates stream durations but is // usually not far from the real duration. diff --git a/Pulsus/Game.cs b/Pulsus/Game.cs index e787674..af3f4e2 100644 --- a/Pulsus/Game.cs +++ b/Pulsus/Game.cs @@ -34,7 +34,9 @@ public Game() FFmpegHelper.Init(); Log.Info("Initializing Audio..."); - audio = new AudioEngine(null, settings.audio.driver, (int)settings.audio.sampleRate, (int)settings.audio.bufferLength); + audio = new AudioEngine(null, settings.audio.driver, + (int)settings.audio.sampleRate, (int)settings.audio.bufferLength, + settings.audio.resampleQuality); audio.SetVolume(Math.Min(settings.audio.volume, 100) / 100.0f); Log.Info("Audio driver: " + audio.audioDriver.ToString()); diff --git a/Pulsus/Pulsus.csproj b/Pulsus/Pulsus.csproj index 3a824da..281039a 100644 --- a/Pulsus/Pulsus.csproj +++ b/Pulsus/Pulsus.csproj @@ -199,6 +199,7 @@ + diff --git a/Pulsus/Shared/ResampleQuality.cs b/Pulsus/Shared/ResampleQuality.cs new file mode 100644 index 0000000..d75b725 --- /dev/null +++ b/Pulsus/Shared/ResampleQuality.cs @@ -0,0 +1,10 @@ +namespace Pulsus +{ + public enum ResampleQuality + { + Low, + Medium, + High, + Highest, + } +} diff --git a/Pulsus/Shared/Settings.cs b/Pulsus/Shared/Settings.cs index ad10be9..b75eddb 100644 --- a/Pulsus/Shared/Settings.cs +++ b/Pulsus/Shared/Settings.cs @@ -1,11 +1,11 @@ -using Jil; +using System; +using System.Collections.Generic; +using Jil; using Pulsus.Audio; using Pulsus.Gameplay; using Pulsus.Graphics; using Pulsus.Input; using SDL2; -using System.Collections.Generic; -using System; namespace Pulsus { @@ -82,10 +82,10 @@ public class VideoSettings public class AudioSettings { public AudioDriver driver = AudioDriver.Default; - public uint volume = 30; public uint sampleRate = 44100; public uint bufferLength = 768; + public ResampleQuality resampleQuality = ResampleQuality.Low; } public class InputSettings diff --git a/Pulsus/Shared/SettingsParser.cs b/Pulsus/Shared/SettingsParser.cs index 2e82cf6..cd4fb5c 100644 --- a/Pulsus/Shared/SettingsParser.cs +++ b/Pulsus/Shared/SettingsParser.cs @@ -32,6 +32,8 @@ private Settings settings } } + private bool resampleQualityOverridden = false; + public Settings Parse(string[] args) { for (int i = 1; i < args.Length; i++) @@ -67,12 +69,17 @@ private static void PrintHelp() Tuple.Create("-m VALUE, --measure VALUE", "Starts the chart from measure number VALUE [0-999]"), Tuple.Create("", ""), Tuple.Create("--render OUTPUT.wav", "Renders all audio of CHARTFILE to file"), + Tuple.Create("--resample-quality [low|medium|high|highest]", + "Changes resampling quality of audio samples"), Tuple.Create("--dump-timestamps OUTPUT", "Dumps all generated note event timestamps of CHARTFILE"), }; foreach (var option in options) { - Console.WriteLine(" {0,-35}{1}", option.Item1, option.Item2); + if (option.Item1.Length < 35) + Console.WriteLine(" {0,-35}{1}", option.Item1, option.Item2); + else + Console.WriteLine(" {0,-35}\n {2,-35}{1}", option.Item1, option.Item2, ""); } Console.Write("\n"); @@ -127,6 +134,20 @@ public bool ParseArg(string key, string value) settings.audio.volume = 100; settings.outputPath = value; + if (!resampleQualityOverridden) + settings.audio.resampleQuality = ResampleQuality.Highest; + break; + case "--resample-quality": + { + ResampleQuality resampleQuality; + if (Enum.TryParse(value, true, out resampleQuality)) + { + settings.audio.resampleQuality = resampleQuality; + resampleQualityOverridden = true; + } + else + Log.Warning("Unknown resample quality value: " + value); + } break; case "--dump-timestamps": settings.outputMode = OutputMode.DumpTimestamps;