From 0396389ff97bfd2fdae36afdf8d120caf0822d19 Mon Sep 17 00:00:00 2001 From: hj615 Date: Thu, 28 Aug 2025 14:38:44 +0900 Subject: [PATCH] =?UTF-8?q?design:=20DataGrid=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=EA=B0=92=20=EC=A4=84=EB=B0=94=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SmartAquaViewer/Classes/RtspImageBehaviour.cs | 11 +- SmartAquaViewer/Controls/FFPlayerControl.xaml | 19 +- SmartAquaViewer/View/CCTVView.xaml | 2 +- SmartAquaViewer/View/EnegyView.xaml | 65 ++++- SmartAquaViewer/View/FileListView.xaml | 12 +- SmartAquaViewer/View/GreenHouseView.xaml | 55 +++- SmartAquaViewer/View/MonitoringView.xaml | 271 ++++++++++++++---- .../ViewModel/FFPlayerViewModel.cs | 212 ++++++-------- .../ViewModel/MonitoringViewModel.cs | 8 + 9 files changed, 432 insertions(+), 223 deletions(-) diff --git a/SmartAquaViewer/Classes/RtspImageBehaviour.cs b/SmartAquaViewer/Classes/RtspImageBehaviour.cs index ff3e728..d3cc4b6 100644 --- a/SmartAquaViewer/Classes/RtspImageBehaviour.cs +++ b/SmartAquaViewer/Classes/RtspImageBehaviour.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Drawing; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -78,9 +79,6 @@ namespace SmartAquaViewer.Classes private readonly Queue _frameQueue = new(); // lock 불필요 private volatile bool _disposed; - // 큐 최대 길이(지연 방지용): 최신 프레임 위주로 보여주기 - private const int MaxQueue = 2; - private object _lockObject = new(); public ImageRtspAdapter(System.Windows.Controls.Image img, string url) @@ -120,7 +118,7 @@ namespace SmartAquaViewer.Classes try { AVFrame convertedFrame = vfc.Convert(frame); - EnqueueFrame(convertedFrame); // 큐 삽입 (길이 제한 적용) + _frameQueue.Enqueue(convertedFrame); // 큐 삽입 (길이 제한 적용) } catch (Exception ex) { @@ -135,11 +133,6 @@ namespace SmartAquaViewer.Classes } } - private void EnqueueFrame(AVFrame avf) - { - _frameQueue.Enqueue(avf); - } - private unsafe void RenderLoop() { try diff --git a/SmartAquaViewer/Controls/FFPlayerControl.xaml b/SmartAquaViewer/Controls/FFPlayerControl.xaml index a152e49..7ee0277 100644 --- a/SmartAquaViewer/Controls/FFPlayerControl.xaml +++ b/SmartAquaViewer/Controls/FFPlayerControl.xaml @@ -6,7 +6,7 @@ xmlns:local="clr-namespace:SmartAquaViewer.Controls" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + @@ -24,25 +24,22 @@ + - - diff --git a/SmartAquaViewer/View/EnegyView.xaml b/SmartAquaViewer/View/EnegyView.xaml index 07265f2..41456dd 100644 --- a/SmartAquaViewer/View/EnegyView.xaml +++ b/SmartAquaViewer/View/EnegyView.xaml @@ -97,29 +97,64 @@ Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}" ElementStyle="{StaticResource DataGridElmenetStyle}"/> - + + + + + + + + + Binding="{Binding Filtering.CirculationPumpEnergy, StringFormat=\{0:F2\}}"/> - - + Binding="{Binding Filtering.HeatPumpEnergy, StringFormat=\{0:F2\}}"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Binding="{Binding Sterilizing.ExcessOzoneDestroyerEnergy, StringFormat=\{0:F2\}}"> - - + + - - diff --git a/SmartAquaViewer/View/FileListView.xaml b/SmartAquaViewer/View/FileListView.xaml index 6188032..dfdee66 100644 --- a/SmartAquaViewer/View/FileListView.xaml +++ b/SmartAquaViewer/View/FileListView.xaml @@ -41,11 +41,21 @@ SelectedItem="{Binding SelectedFile}" ScrollViewer.VerticalScrollBarVisibility="Hidden" BorderThickness="0" Background="Transparent"> + + + + FontSize="16" FontWeight="Medium" Foreground="White"/> diff --git a/SmartAquaViewer/View/GreenHouseView.xaml b/SmartAquaViewer/View/GreenHouseView.xaml index 7c9240d..4621962 100644 --- a/SmartAquaViewer/View/GreenHouseView.xaml +++ b/SmartAquaViewer/View/GreenHouseView.xaml @@ -97,16 +97,37 @@ Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}" ElementStyle="{StaticResource DataGridElmenetStyle}"/> - + + + + + + + + - - + + + + + + + + + + + + + + + + @@ -116,10 +137,24 @@ - - + + + + + + + + + + + + + + + + diff --git a/SmartAquaViewer/View/MonitoringView.xaml b/SmartAquaViewer/View/MonitoringView.xaml index 2dbdf85..81c7a63 100644 --- a/SmartAquaViewer/View/MonitoringView.xaml +++ b/SmartAquaViewer/View/MonitoringView.xaml @@ -43,53 +43,136 @@ - + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - - - - + ElementStyle="{StaticResource DataGridElmenetStyle}"/> + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - - - + ElementStyle="{StaticResource DataGridElmenetStyle}"/> + + + + + + + + + + + + + + + + + + + + + + + + @@ -105,30 +188,114 @@ Binding="{Binding RecordedTime, StringFormat=\{0:HH:mm:ss\}}" ElementStyle="{StaticResource DataGridElmenetStyle}"/> - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SmartAquaViewer/ViewModel/FFPlayerViewModel.cs b/SmartAquaViewer/ViewModel/FFPlayerViewModel.cs index e3e9757..89d6fae 100644 --- a/SmartAquaViewer/ViewModel/FFPlayerViewModel.cs +++ b/SmartAquaViewer/ViewModel/FFPlayerViewModel.cs @@ -6,9 +6,12 @@ using System.Diagnostics; using System.Drawing; using System.Linq; using System.Runtime.CompilerServices; +using System.Security.Policy; using System.Text; using System.Threading.Tasks; +using System.Windows; using System.Windows.Media; +using System.Windows.Media.Imaging; using System.Windows.Threading; using FFmpeg.AutoGen; using SmartAquaViewer.Helper.FFHelper; @@ -27,14 +30,16 @@ namespace SmartAquaViewer.ViewModel set { _currentFrame = value; OnPropertyChanged(); } } + //private readonly System.Windows.Controls.Image _img; + private readonly object _lockObject = new object(); private Thread _videoThread; private Thread _renderingThread; - private CancellationTokenSource _videoCancellationTokenSource; - private CancellationTokenSource _renderingCancellationTokenSource; - private bool _stopThread = false; + private CancellationTokenSource _videoCts; + private CancellationTokenSource _renderCts; - private ConcurrentQueue _frameQueue = new ConcurrentQueue(); + private readonly Queue _frameQueue = new(); // lock 불필요 + private volatile bool _disposed; public FFPlayerViewModel(CCTVInfo cctvInfo) { @@ -43,172 +48,131 @@ namespace SmartAquaViewer.ViewModel public void StartMedia(string rtspURL) { - ClosePlayer(); - _stopThread = false; + _videoCts = new CancellationTokenSource(); + _renderCts = new CancellationTokenSource(); - _videoThread = new Thread(new ThreadStart(OpenMedia)); - _renderingThread = new Thread(new ThreadStart(RenderImage)); + _videoThread = new Thread(OpenMedia) { IsBackground = true, Name = "RTSP-Decode" }; + _renderingThread = new Thread(RenderImage) { IsBackground = true, Name = "RTSP-Render" }; - _videoThread.Priority = ThreadPriority.Highest; // 우선순위 설정 _videoThread.Start(); - _renderingThread.Start(); } private unsafe void OpenMedia() { - int failCount = 0; - int frameCount = 0; - - _frameQueue = new ConcurrentQueue(); - _videoCancellationTokenSource = new CancellationTokenSource(); - try { - using (StreamDecoder sd = new StreamDecoder(CCTVInfo.RtspUrl)) + using (var sd = new StreamDecoder(CCTVInfo.RtspUrl!)) + using (var vfc = new VideoFrameConverter(sd.FrameSize, sd.PixelFormat, sd.FrameSize, AVPixelFormat.AV_PIX_FMT_BGR24)) { - using (var vfc = new VideoFrameConverter(sd.FrameSize, sd.PixelFormat, sd.FrameSize, AVPixelFormat.AV_PIX_FMT_BGR24)) + while (!_videoCts!.IsCancellationRequested) { - while (!_videoCancellationTokenSource.Token.IsCancellationRequested) + if (!sd.TryDecodeNextFrame(out var frame)) { - bool decodeSuccess = sd.TryDecodeNextFrame(out var frame); - - //if (!decodeSuccess) - //{ - // failCount++; - // HandleDecodeFailure(ref failCount, maxFailCount); - // continue; // 다음 반복으로 이동 - //} - - // 디코딩 성공 시 프레임 처리 - HandleDecodedFrame(frame, vfc, ref frameCount); - failCount = 0; // 실패 카운트 초기화 + // 디코드 실패: 너무 바쁘지 않게 살짝 쉼 + Thread.Sleep(2); + continue; } - } - } - } - catch (Exception ex) - { - //Log4NetManager.GetLog().Error("OpenMedia() : " + ex.Message); - Debug.WriteLine("OpenMedia() : " + ex.Message.ToString()); - } - finally - { - if (!_stopThread || !_videoCancellationTokenSource.Token.IsCancellationRequested) - { - Debug.WriteLine($"Restarting media"); - StartMedia(CCTVInfo.RtspUrl); - } - } - } - - private void HandleDecodedFrame(AVFrame frame, VideoFrameConverter vfc, ref int frameCount) - { - Bitmap convertedFrame = null; - try - { - convertedFrame = vfc.DeepCopyFrame(frame); - - lock (_lockObject) - { - _frameQueue.Enqueue(convertedFrame); - frameCount++; + try + { + AVFrame convertedFrame = vfc.Convert(frame); + _frameQueue.Enqueue(convertedFrame); // 큐 삽입 (길이 제한 적용) + } + catch (Exception ex) + { + Debug.WriteLine("Decode/Enqueue error: " + ex.Message); + } + } } } catch (Exception ex) { - //Log4NetManager.GetLog().Error("HandleDecodedFrame() : " + ex.Message); - Debug.WriteLine("HandleDecodedFrame() : " + ex.Message); - convertedFrame?.Dispose(); + Debug.WriteLine("OpenMedia() : " + ex); } } - private void RenderImage() + private unsafe void RenderImage() { - int dequeCount = 0; - - _renderingCancellationTokenSource = new CancellationTokenSource(); - try { - while (!_renderingCancellationTokenSource.Token.IsCancellationRequested) + while (!_renderCts!.IsCancellationRequested) { - Bitmap bitmap = null; - - lock (_lockObject) + if (_frameQueue.Count > 0) { - if (_frameQueue.Count > 0) + AVFrame convertedFrame; + lock (_lockObject) { - _frameQueue.TryDequeue(out bitmap); + if (_frameQueue.Count > 0) + { + convertedFrame = _frameQueue.Dequeue(); + } + else + { + continue; + } } - else + try { - continue; - } - } + Bitmap bitmap = new Bitmap(convertedFrame.width, convertedFrame.height, convertedFrame.linesize[0], System.Drawing.Imaging.PixelFormat.Format24bppRgb, (IntPtr)convertedFrame.data[0]); - if (bitmap == null) - { - Thread.Sleep(10); // CPU 과부하 방지 - continue; - } + var src = CreateBitmapSource(bitmap); + src.Freeze(); - try - { - //Dispatcher.BeginInvoke((Action)(() => - //{ - // try - // { - // DivideAndDisplayBitmap(bitmap); - // } - // finally - // { - // bitmap.Dispose(); // 자원 해제 - // } - //})); - } - catch (ArgumentException ex) - { - Console.WriteLine("RenderImage() : " + ex.Message); - } - catch (Exception ex) - { - Console.WriteLine("RenderImage() : " + ex.ToString()); + CurrentFrame!.Dispatcher.BeginInvoke(new Action(() => + { + if (_disposed) return; + CurrentFrame = src; + }), DispatcherPriority.Render); + } + catch (Exception ex) + { + Debug.WriteLine("RenderLoop() : " + ex.Message); + } } - - _renderingCancellationTokenSource.Token.ThrowIfCancellationRequested(); } } - catch (OperationCanceledException) + catch (OperationCanceledException) { /* 정상 종료 */ } + finally + { + //ClearQueue(); + } + } + + private static BitmapSource CreateBitmapSource(Bitmap bitmap) + { + // GDI 핸들 사용 X. LockBits → BitmapSource.Create 경로만 사용. + var rect = new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height); + var data = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat); + try { - Console.WriteLine("RenderImage() : Render loop canceled."); + // Format24bppRgb ↔ PixelFormats.Bgr24 매칭 + return BitmapSource.Create( + data.Width, data.Height, 96, 96, + PixelFormats.Bgr24, null, + data.Scan0, data.Stride * data.Height, data.Stride); } finally { - // 필요한 자원 정리 - //ClearFrameQueue(); + bitmap.UnlockBits(data); } } public void ClosePlayer() { - _stopThread = true; + if (_disposed) return; + _disposed = true; - lock (_lockObject) - { - if (_videoCancellationTokenSource != null) - { - _videoCancellationTokenSource.Cancel(); - Debug.WriteLine("ClosePlayer(): videoThread 종료함"); - } + try { _videoCts?.Cancel(); } catch { } + try { _renderCts?.Cancel(); } catch { } - if (_renderingCancellationTokenSource != null) - { - _renderingCancellationTokenSource.Cancel(); - Debug.WriteLine("ClosePlayer(): RenderingThread 종료함"); - } - } + try { if (_videoThread?.IsAlive == true) _videoThread.Join(300); } catch { } + try { if (_renderingThread?.IsAlive == true) _renderingThread.Join(300); } catch { } + + _frameQueue.Clear(); + + _videoCts?.Dispose(); + _renderCts?.Dispose(); } public event PropertyChangedEventHandler? PropertyChanged; diff --git a/SmartAquaViewer/ViewModel/MonitoringViewModel.cs b/SmartAquaViewer/ViewModel/MonitoringViewModel.cs index 1d020da..690dae7 100644 --- a/SmartAquaViewer/ViewModel/MonitoringViewModel.cs +++ b/SmartAquaViewer/ViewModel/MonitoringViewModel.cs @@ -304,9 +304,17 @@ namespace SmartAquaViewer.ViewModel { var tbt = new TanksByTime(w.RecordedTime, w.Tanks); TanksByTimes.Add(tbt); + OrderTanksBtTimeByDate(); } } + private void OrderTanksBtTimeByDate() + { + var ordered = TanksByTimes.OrderBy(t => t.RecordedTime).ToList(); + TanksByTimes.Clear(); + foreach (var t in ordered) TanksByTimes.Add(t); + } + private void DrawGraph(object obj) { switch (SelectedGraphType)