From 201e65b3b535763e68764a083851e1e00775e9b4 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 18 Jan 2017 19:44:39 +0100 Subject: [PATCH] Performance improvements --- Artemis/Artemis/Artemis.csproj | 2 +- .../DeviceProviders/KeyboardProvider.cs | 8 +- .../Artemis/InjectionModules/BaseModules.cs | 4 +- .../InjectionModules/ManagerModules.cs | 1 + .../AudioCaptureManager.cs | 3 +- Artemis/Artemis/Managers/LoopManager.cs | 24 +++--- .../Artemis/Modules/Abstract/ModuleModel.cs | 4 +- .../GeneralProfile/GeneralProfileDataModel.cs | 10 +-- .../GeneralProfile/GeneralProfileModel.cs | 42 +++++----- .../Profiles/Layers/Models/TweenModel.cs | 76 ++++++++++++++----- .../Audio/AudioCapturing/AudioCapture.cs | 60 ++++++++++++++- .../Profiles/Layers/Types/Audio/AudioType.cs | 2 + Artemis/Artemis/Utilities/ImageUtilities.cs | 33 +++++--- 13 files changed, 175 insertions(+), 94 deletions(-) rename Artemis/Artemis/{Profiles/Layers/Types/Audio/AudioCapturing => Managers}/AudioCaptureManager.cs (94%) diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index bc09abb0e..4642e480e 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -503,7 +503,7 @@ - + diff --git a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs index 59b4889fa..60b146fdb 100644 --- a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs +++ b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs @@ -27,17 +27,11 @@ namespace Artemis.DeviceProviders public abstract void Enable(); public abstract void DrawBitmap(Bitmap bitmap); - /// - /// Returns a bitmap matching the keyboard's dimensions - /// - /// - public Bitmap KeyboardBitmap() => new Bitmap(Width, Height); - /// /// Returns a bitmap matching the keyboard's dimensions using the provided scale /// /// - public Bitmap KeyboardBitmap(int scale) => new Bitmap(Width*scale, Height*scale); + public Bitmap KeyboardBitmap(int scale = 4) => new Bitmap(Width*scale, Height*scale); public Rect KeyboardRectangle(int scale = 4) => new Rect(new Size(Width*scale, Height*scale)); diff --git a/Artemis/Artemis/InjectionModules/BaseModules.cs b/Artemis/Artemis/InjectionModules/BaseModules.cs index eb4bc0bad..7447fb67d 100644 --- a/Artemis/Artemis/InjectionModules/BaseModules.cs +++ b/Artemis/Artemis/InjectionModules/BaseModules.cs @@ -1,4 +1,5 @@ using Artemis.DeviceProviders; +using Artemis.Managers; using Artemis.Models; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Interfaces; @@ -104,9 +105,6 @@ namespace Artemis.InjectionModules .InheritedFrom() .BindToSelf()); - // Type helpers - Bind().ToSelf().InSingletonScope(); - #endregion #region Lua diff --git a/Artemis/Artemis/InjectionModules/ManagerModules.cs b/Artemis/Artemis/InjectionModules/ManagerModules.cs index b01f7fab9..6f6b1c1b3 100644 --- a/Artemis/Artemis/InjectionModules/ManagerModules.cs +++ b/Artemis/Artemis/InjectionModules/ManagerModules.cs @@ -13,6 +13,7 @@ namespace Artemis.InjectionModules Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); + Bind().ToSelf().InSingletonScope(); } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs b/Artemis/Artemis/Managers/AudioCaptureManager.cs similarity index 94% rename from Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs rename to Artemis/Artemis/Managers/AudioCaptureManager.cs index de8ccfa2a..52e063838 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs +++ b/Artemis/Artemis/Managers/AudioCaptureManager.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Timers; using Artemis.Events; +using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using CSCore.CoreAudioAPI; using Ninject.Extensions.Logging; -namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +namespace Artemis.Managers { public class AudioCaptureManager { diff --git a/Artemis/Artemis/Managers/LoopManager.cs b/Artemis/Artemis/Managers/LoopManager.cs index 0066a1394..10500499a 100644 --- a/Artemis/Artemis/Managers/LoopManager.cs +++ b/Artemis/Artemis/Managers/LoopManager.cs @@ -52,8 +52,8 @@ namespace Artemis.Managers //TODO DarthAffe 14.01.2017: A stop-condition and a real cleanup instead of just aborting might be better while (true) { - try - { +// try +// { long preUpdateTicks = DateTime.Now.Ticks; Render(); @@ -61,11 +61,11 @@ namespace Artemis.Managers int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f)); if (sleep > 0) Thread.Sleep(sleep); - } - catch (Exception e) - { - _logger.Warn(e, "Exception in render loop"); - } +// } +// catch (Exception e) +// { +// _logger.Warn(e, "Exception in render loop"); +// } } // ReSharper disable once FunctionNeverReturns } @@ -207,19 +207,19 @@ namespace Artemis.Managers if (keyboard == null) return; - KeyboardBitmap = keyboard.KeyboardBitmap(4); + KeyboardBitmap = keyboard.KeyboardBitmap(); KeyboardBitmap.SetResolution(96, 96); - MouseBitmap = new Bitmap(40, 40); + MouseBitmap = new Bitmap(10, 10); MouseBitmap.SetResolution(96, 96); - HeadsetBitmap = new Bitmap(40, 40); + HeadsetBitmap = new Bitmap(10, 10); HeadsetBitmap.SetResolution(96, 96); - GenericBitmap = new Bitmap(40, 40); + GenericBitmap = new Bitmap(10, 10); GenericBitmap.SetResolution(96, 96); - MousematBitmap = new Bitmap(40, 40); + MousematBitmap = new Bitmap(10, 10); MousematBitmap.SetResolution(96, 96); using (var g = Graphics.FromImage(KeyboardBitmap)) diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index 7f4209637..9320684b6 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -163,7 +163,7 @@ namespace Artemis.Modules.Abstract ProfileModel?.DrawLayers(g, layers, DrawType.Keyboard, DataModel, keyboardRect, preview); } // Render mice layer-by-layer - var devRec = new Rect(0, 0, 40, 40); + var devRec = new Rect(0, 0, 10, 10); using (var g = Graphics.FromImage(frame.MouseBitmap)) { ProfileModel?.DrawLayers(g, layers, DrawType.Mouse, DataModel, devRec, preview); @@ -183,7 +183,7 @@ namespace Artemis.Modules.Abstract { ProfileModel?.DrawLayers(g, layers, DrawType.Mousemat, DataModel, devRec, preview); } - + // Trace debugging if (DateTime.Now.AddSeconds(-2) <= _lastTrace || Logger == null) return; diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs index 70e9dcd46..443be1c79 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileDataModel.cs @@ -40,14 +40,8 @@ namespace Artemis.Modules.General.GeneralProfile public class AudioDevice { public float OverallPeak { get; set; } - public float Channel1Peak { get; set; } - public float Channel2Peak { get; set; } - public float Channel3Peak { get; set; } - public float Channel4Peak { get; set; } - public float Channel5Peak { get; set; } - public float Channel6Peak { get; set; } - public float Channel7Peak { get; set; } - public float Channel8Peak { get; set; } + public float LeftPeak { get; set; } + public float RightPeak { get; set; } } [MoonSharpUserData] diff --git a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs index 587741268..e0c0a5e60 100644 --- a/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs +++ b/Artemis/Artemis/Modules/General/GeneralProfile/GeneralProfileModel.cs @@ -10,7 +10,6 @@ using Artemis.DAL; using Artemis.Events; using Artemis.Managers; using Artemis.Modules.Abstract; -using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Utilities; using CSCore.CoreAudioAPI; using Newtonsoft.Json; @@ -20,7 +19,6 @@ namespace Artemis.Modules.General.GeneralProfile { public class GeneralProfileModel : ModuleModel { - private readonly AudioCaptureManager _audioCaptureManager; private DateTime _lastMusicUpdate; private SpotifyLocalAPI _spotify; private bool _spotifySetupBusy; @@ -28,7 +26,6 @@ namespace Artemis.Modules.General.GeneralProfile public GeneralProfileModel(DeviceManager deviceManager, LuaManager luaManager, AudioCaptureManager audioCaptureManager) : base(deviceManager, luaManager) { - _audioCaptureManager = audioCaptureManager; _lastMusicUpdate = DateTime.Now; Settings = SettingsProvider.Load(); @@ -93,31 +90,26 @@ namespace Artemis.Modules.General.GeneralProfile private void UpdateAudio(GeneralProfileDataModel dataModel) { - - var recording = AudioMeterInformation.FromDevice(_defaultRecording); - var playback = AudioMeterInformation.FromDevice(_defaultPlayback); + // Update microphone, only bother with OverallPeak + if (_defaultRecording != null) + { + var recording = AudioMeterInformation.FromDevice(_defaultRecording); + dataModel.Audio.Recording.OverallPeak = recording.PeakValue; + } + + if (_defaultPlayback == null) + return; + + // Update volume if a default device is found dataModel.Audio.Volume = AudioEndpointVolume.FromDevice(_defaultPlayback).GetMasterVolumeLevelScalar(); - dataModel.Audio.Recording.OverallPeak = recording.PeakValue; - for (var i = 0; i < recording.GetChannelsPeakValues(recording.MeteringChannelCount).Length; i++) - { - // Only support up to 8 channels until lists are supported natively - if (i > 7) - break; - - var peakValue = recording.GetChannelsPeakValues(recording.MeteringChannelCount)[i]; - typeof(AudioDevice).GetProperty($"Channel{i + 1}Peak").SetValue(dataModel.Audio.Recording, peakValue); - } + // Update speakers, only do overall, left and right for now + // TODO: When adding list support lets do all channels + var playback = AudioMeterInformation.FromDevice(_defaultPlayback); + var peakValues = playback.GetChannelsPeakValues(); dataModel.Audio.Playback.OverallPeak = playback.PeakValue; - for (var i = 0; i < playback.GetChannelsPeakValues(playback.MeteringChannelCount).Length; i++) - { - // Only support up to 8 channels until lists are supported natively - if (i > 7) - break; - - var peakValue = playback.GetChannelsPeakValues(playback.MeteringChannelCount)[i]; - typeof(AudioDevice).GetProperty($"Channel{i + 1}Peak").SetValue(dataModel.Audio.Playback, peakValue); - } + dataModel.Audio.Playback.LeftPeak = peakValues[0]; + dataModel.Audio.Playback.LeftPeak = peakValues[1]; } #endregion diff --git a/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs b/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs index 29e679164..70bb2b63f 100644 --- a/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Models/TweenModel.cs @@ -24,51 +24,87 @@ namespace Artemis.Profiles.Layers.Models _yTweener = new Tweener((float) layerModel.Y, (float) layerModel.Y, 0); _widthTweener = new Tweener((float) layerModel.Width, (float) layerModel.Width, 0); _heightTweener = new Tweener((float) layerModel.Height, (float) layerModel.Height, 0); - _opacityTweener = new Tweener((float) layerModel.Opacity, (float) layerModel.Opacity, 0); - - StoreCurrentValues(); + _opacityTweener = new Tweener((float) layerModel.Opacity, (float) layerModel.Opacity, 0); + + _x = (float)_layerModel.X; + _y = (float)_layerModel.Y; + _width = (float)_layerModel.Width; + _height = (float)_layerModel.Height; + _opacity = (float)_layerModel.Opacity; } public void Update() { - // Width + UpdateWidth(); + UpdateHeight(); + UpdateOpacity(); + } + + private void UpdateWidth() + { + if (Math.Abs(_layerModel.Properties.WidthEaseTime) < 0.001) + return; + + // Width if (Math.Abs(_layerModel.Width - _width) > 0.001) { var widthFunc = GetEaseFunction(_layerModel.Properties.WidthEase); var widthSpeed = _layerModel.Properties.WidthEaseTime; - _xTweener = new Tweener(_xTweener.Value, (float) _layerModel.X, widthSpeed, widthFunc); - _widthTweener = new Tweener(_widthTweener.Value, (float) _layerModel.Width, widthSpeed, widthFunc); - } + _xTweener = new Tweener(_xTweener.Value, (float)_layerModel.X, widthSpeed, widthFunc); + _widthTweener = new Tweener(_widthTweener.Value, (float)_layerModel.Width, widthSpeed, widthFunc); + } + + _xTweener.Update(40); + _widthTweener.Update(40); + + _x = (float) _layerModel.X; + _width = (float)_layerModel.Width; + + _layerModel.X = _xTweener.Value; + _layerModel.Width = _widthTweener.Value; + } + + private void UpdateHeight() + { + if (Math.Abs(_layerModel.Properties.HeightEaseTime) < 0.001) + return; // Height if (Math.Abs(_layerModel.Height - _height) > 0.001) { var heightFunc = GetEaseFunction(_layerModel.Properties.HeightEase); var heightSpeed = _layerModel.Properties.HeightEaseTime; - _yTweener = new Tweener(_y, (float) _layerModel.Y, heightSpeed, heightFunc); - _heightTweener = new Tweener(_height, (float) _layerModel.Height, heightSpeed, heightFunc); - } + _yTweener = new Tweener(_y, (float)_layerModel.Y, heightSpeed, heightFunc); + _heightTweener = new Tweener(_height, (float)_layerModel.Height, heightSpeed, heightFunc); + } + + _yTweener.Update(40); + _heightTweener.Update(40); + + _y = (float)_layerModel.Y; + _height = (float)_layerModel.Height; + + _layerModel.Y = _yTweener.Value; + _layerModel.Height = _heightTweener.Value; + } + + private void UpdateOpacity() + { + if (Math.Abs(_layerModel.Properties.OpacityEaseTime) < 0.001) + return; // Opacity if (Math.Abs(_layerModel.Opacity - _opacity) > 0.001) { - _opacityTweener = new Tweener(_opacity, (float) _layerModel.Opacity, + _opacityTweener = new Tweener(_opacity, (float)_layerModel.Opacity, _layerModel.Properties.OpacityEaseTime, GetEaseFunction(_layerModel.Properties.OpacityEase)); } - _xTweener.Update(40); - _yTweener.Update(40); - _widthTweener.Update(40); - _heightTweener.Update(40); _opacityTweener.Update(40); - StoreCurrentValues(); + _opacity = (float)_layerModel.Opacity; - _layerModel.X = _xTweener.Value; - _layerModel.Y = _yTweener.Value; - _layerModel.Width = _widthTweener.Value; - _layerModel.Height = _heightTweener.Value; _layerModel.Opacity = _opacityTweener.Value; } diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs index 179bd584e..29d488632 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -12,9 +12,11 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing { public class AudioCapture { - private const FftSize FftSize = CSCore.DSP.FftSize.Fft4096; + private const FftSize FftSize = CSCore.DSP.FftSize.Fft1024; private readonly Timer _volumeTimer; private readonly double[] _volumeValues; + private readonly Timer _disableTimer; + private bool _mayStop; private SingleSpectrum _singleSpectrum; private WasapiLoopbackCapture _soundIn; private GainSource _source; @@ -30,11 +32,12 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing _volumeValues = new double[5]; _volumeIndex = 0; + _disableTimer = new Timer(1000); + _disableTimer.Elapsed += CheckStop; _volumeTimer = new Timer(200); _volumeTimer.Elapsed += VolumeTimerOnElapsed; - Start(); } - + public ILogger Logger { get; } public MMDevice Device { get; } public double DesiredAverage { get; set; } @@ -45,6 +48,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing set { _volume.Volume = value; } } + public bool Running { get; set; } + private void VolumeTimerOnElapsed(object sender, ElapsedEventArgs e) { if (Volume <= 0) @@ -100,12 +105,36 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing }; } + /// + /// Keeps the audio capture active, when not called for longer than 1 sec the capture will + /// stop capturing until Pulse is called again + /// + public void Pulse() + { + _mayStop = false; + if (!Running) + Start(); + } + + private void CheckStop(object sender, ElapsedEventArgs e) + { + if (_mayStop) + { + Logger.Debug("Stopping idle audio capture for device: {0}", Device?.FriendlyName ?? "default"); + Stop(); + } + else + _mayStop = true; + } + private void Start() { Logger.Debug("Starting audio capture for device: {0}", Device?.FriendlyName ?? "default"); try { + Stop(); + _soundIn = new WasapiLoopbackCapture(); _soundIn.Initialize(); // Not sure if this null check is needed but doesnt hurt @@ -135,14 +164,39 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing }; _singleSpectrum = new SingleSpectrum(FftSize, _spectrumProvider); + _mayStop = false; + _disableTimer.Start(); _volumeTimer.Start(); _soundIn.Start(); + + Running = true; } catch (Exception e) { Logger.Warn(e, "Failed to start WASAPI audio capture"); } } + + private void Stop() + { + Running = false; + + + if (_soundIn != null) + { + _soundIn.Stop(); + _soundIn.Dispose(); + _soundIn = null; + } + if (_source != null) + { + _source.Dispose(); + _source = null; + } + + _disableTimer.Stop(); + _volumeTimer.Stop(); + } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index 2e52bff3c..f7e0a5c16 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Windows; using System.Windows.Media; using Artemis.Events; +using Artemis.Managers; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Animations; @@ -122,6 +123,7 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) { layerModel.ApplyProperties(true); + _audioCapture.Pulse(); var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; diff --git a/Artemis/Artemis/Utilities/ImageUtilities.cs b/Artemis/Artemis/Utilities/ImageUtilities.cs index d8ac1122d..ad33435cf 100644 --- a/Artemis/Artemis/Utilities/ImageUtilities.cs +++ b/Artemis/Artemis/Utilities/ImageUtilities.cs @@ -4,11 +4,15 @@ using System.IO; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; +using PixelFormat = System.Drawing.Imaging.PixelFormat; +using Point = System.Drawing.Point; namespace Artemis.Utilities { public class ImageUtilities { + private static RenderTargetBitmap _rBmp; + /// /// Resize the image to the specified width and height. /// @@ -56,21 +60,26 @@ namespace Artemis.Utilities public static Bitmap DrawingVisualToBitmap(DrawingVisual visual, Rect rect) { - // TODO: Improve performance by dividing by 4 here - var bmp = new RenderTargetBitmap((int) rect.Width, (int) rect.Height, 96, 96, PixelFormats.Pbgra32); - bmp.Render(visual); + var width = (int) rect.Width; + var height = (int) rect.Height; - var encoder = new BmpBitmapEncoder(); - encoder.Frames.Add(BitmapFrame.Create(bmp)); + // RenderTargetBitmap construction is expensive, only do it when needed + if (_rBmp?.PixelHeight != height || _rBmp?.PixelWidth != width) + _rBmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); - Bitmap bitmap; - using (var stream = new MemoryStream()) - { - encoder.Save(stream); - bitmap = new Bitmap(stream); - } + _rBmp.Render(visual); + return GetBitmap(_rBmp); + } - return bitmap; + private static Bitmap GetBitmap(BitmapSource source) + { + var bmp = new Bitmap(source.PixelWidth, source.PixelHeight, PixelFormat.Format32bppPArgb); + bmp.SetResolution(96, 96); + var data = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, + PixelFormat.Format32bppPArgb); + source.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride); + bmp.UnlockBits(data); + return bmp; } ///