diff --git a/CollimationCircles/Services/AppService.cs b/CollimationCircles/Services/AppService.cs index 73e4986..92046da 100644 --- a/CollimationCircles/Services/AppService.cs +++ b/CollimationCircles/Services/AppService.cs @@ -41,7 +41,7 @@ public static string GetAppVersion() var assemblyVersion = entryAssembly?.GetName().Version; return assemblyVersion?.ToString() ?? "0.0.0"; - } + } public static T? Deserialize(string jsonState) { @@ -183,7 +183,7 @@ public static async Task OpenFileBrowser(string path) folderOpener.StartInfo.UseShellExecute = true; folderOpener.Start(); await folderOpener.WaitForExitAsync(); - } + } public static Task<(int, string, Process)> ExecuteCommand(string fileName, List arguments, Action? started = null, int timeout = -1) { @@ -195,7 +195,7 @@ public static async Task OpenFileBrowser(string path) UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true - }; + }; using Process process = new() { @@ -277,7 +277,7 @@ public static async Task OpenFileBrowser(string path) } return tcs.Task; - } + } public static void OpenUrl(string url) { @@ -300,7 +300,7 @@ public static void OpenUrl(string url) } logger.Trace($"External url '{url}' opened"); - } + } public static string? GetLocalIPAddress() { @@ -309,4 +309,32 @@ public static void OpenUrl(string url) IPEndPoint? endPoint = socket.LocalEndPoint as IPEndPoint; return endPoint?.Address.ToString(); } + + public static void StartRaspberryPIStream(string port, List? streamArgs = null) + { + //rpicam-vid -t 0 --inline --listen -n -o tcp://0.0.0.0:5000 + + ExecuteCommand("pkill", ["rpicam-vid"], timeout: 0); + + List parameters = [ + "-t", + "0", + "--inline", + "--listen", + "-n", + "-o", + $"tcp://0.0.0.0:{port}" + ]; + + if (streamArgs != null) + { + parameters.AddRange(streamArgs); + } + + ExecuteCommand( + "rpicam-vid", + parameters, timeout: 0); + + Thread.Sleep(1000); + } } diff --git a/CollimationCircles/Services/CameraControlService.cs b/CollimationCircles/Services/CameraControlService.cs index abea7cd..3891032 100644 --- a/CollimationCircles/Services/CameraControlService.cs +++ b/CollimationCircles/Services/CameraControlService.cs @@ -10,7 +10,7 @@ internal class CameraControlService : ICameraControlService, IDisposable private readonly object vc = new(); - private readonly Dictionary v4l2Properties = new() + private readonly Dictionary v4l2controls = new() { { "Brightness", "brightness" }, { "Contrast", "contrast" }, @@ -28,7 +28,7 @@ internal class CameraControlService : ICameraControlService, IDisposable { "Zoom", "zoom_absolute" } }; - private readonly Dictionary OpenCVProperties = new() + private readonly Dictionary uvcControls = new() { { "Brightness", VideoCaptureProperties.Brightness }, { "Contrast", VideoCaptureProperties.Contrast }, @@ -46,6 +46,20 @@ internal class CameraControlService : ICameraControlService, IDisposable { "Zoom", VideoCaptureProperties.Zoom } }; + private static readonly Dictionary rpiControls = new() + { + { "Brightness", "brightness" }, + { "Contrast", "contrast" }, + { "Saturation", "saturation" }, + { "Gain", "gain" }, + { "Autofocus", "autofocus-mode" }, // default or manual + { "Focus", "lens-position" }, + { "AutoWhiteBalance", "awb" }, // auto 2500K to 8000K, incandescent 2500K to 3000K, tungsten 3000K to 3500K, fluorescent 4000K to 4700K, indoor 3000K to 5000K, daylight 5500K to 6500K, cloudy 7000K to 8500K + { "Sharpness", "sharpness" }, + { "ExposureTime", "shutter" }, + { "Zoom", "roi" } + }; + public CameraControlService() { if (OperatingSystem.IsWindows()) @@ -53,14 +67,17 @@ public CameraControlService() vc = new VideoCapture(); } } - public void Set(string propertyname, double value) + public void Set(string propertyname, double value, StreamSource streamSource) { if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { - AppService.ExecuteCommand("v4l2-ctl", [ - $"--set-ctrl={v4l2Properties[propertyname]}={value}" - ]); - logger.Info($"v4l2-ctl property '{propertyname}' set to '{value}'"); + if (streamSource is StreamSource.UVC) + { + AppService.ExecuteCommand("v4l2-ctl", [ + $"--set-ctrl={v4l2controls[propertyname]}={value}" + ]); + logger.Info($"v4l2-ctl property '{propertyname}' set to '{value}'"); + } } if (OperatingSystem.IsWindows()) @@ -69,7 +86,7 @@ public void Set(string propertyname, double value) { if (((VideoCapture)vc).IsOpened()) { - ((VideoCapture)vc).Set(OpenCVProperties[propertyname], value); + ((VideoCapture)vc).Set(uvcControls[propertyname], value); logger.Info($"OpenCvSharp.VideoCapture property '{propertyname}' set to '{value}'"); } } @@ -106,5 +123,64 @@ public void Release() ((VideoCapture)vc)?.Release(); } } + + public static List GetRaspberryPIControls(decimal? brightness = null, decimal? contrast = null, decimal? saturation = null, + decimal? gain = null, bool autofocusMode = true, decimal? focus = null, decimal? whiteBalance = null, decimal? sharpness = null, + decimal? exposureTime = null, decimal? zoom = null) + { + List controls = new(); + + if (brightness != null) + controls.Add($"--{rpiControls["Brightness"]} {brightness}"); + + if (contrast != null) + controls.Add($"--{rpiControls["Contrast"]} {contrast}"); + + if (saturation != null) + controls.Add($"--{rpiControls["Saturation"]} {contrast}"); + + if (gain != null) + controls.Add($"--{rpiControls["Gain"]} {contrast}"); + + var afMode = autofocusMode ? "auto" : "manual"; + controls.Add($"--{rpiControls["Autofocus"]} {afMode}"); + + if (focus != null) + controls.Add($"--{rpiControls["Focus"]} {focus}"); + + string wb = "auto"; + + if (whiteBalance >= 2500 && whiteBalance <= 3000) + wb = "incandescent"; + + if (whiteBalance >= 3000 && whiteBalance <= 3500) + wb = "tungsten"; + + if (whiteBalance >= 4000 && whiteBalance <= 4700) + wb = "fluorescent"; + + if (whiteBalance >= 3000 && whiteBalance <= 5000) + wb = "indoor"; + + if (whiteBalance >= 5500 && whiteBalance <= 6500) + wb = "daylight"; + + if (whiteBalance >= 7000 && whiteBalance <= 8500) + wb = "cloudy"; + + controls.Add($"--{rpiControls["AutoWhiteBalance"]} {wb}"); + + if (sharpness != null) + controls.Add($"--{rpiControls["Sharpness"]} {sharpness}"); + + if (exposureTime != null) + controls.Add($"--{rpiControls["ExposureTime"]} {exposureTime}"); + + decimal? roi = 1.0M / zoom; + if (zoom != null) + controls.Add($"--{rpiControls["Zoom"]} {roi},{roi},{roi},{roi}"); + + return controls; + } } } diff --git a/CollimationCircles/Services/ICameraControlService.cs b/CollimationCircles/Services/ICameraControlService.cs index 4992eb6..60d5af2 100644 --- a/CollimationCircles/Services/ICameraControlService.cs +++ b/CollimationCircles/Services/ICameraControlService.cs @@ -1,10 +1,11 @@ using System; +using System.Collections.Generic; namespace CollimationCircles.Services { public interface ICameraControlService { - public void Set(string propertyname, double value); + public void Set(string propertyname, double value, StreamSource streamSource); public void Open(); public void Release(); } diff --git a/CollimationCircles/Services/ILibVLCService.cs b/CollimationCircles/Services/ILibVLCService.cs index 4f93c96..661380b 100644 --- a/CollimationCircles/Services/ILibVLCService.cs +++ b/CollimationCircles/Services/ILibVLCService.cs @@ -11,7 +11,8 @@ public interface ILibVLCService { public string FullAddress { get; set; } public MediaPlayer MediaPlayer { get; } - public void Play(); + public StreamSource StreamSource { get; set; } + public void Play(List controlsArgs); public string DefaultAddress(StreamSource streamSource); } } diff --git a/CollimationCircles/Services/LibVLCService.cs b/CollimationCircles/Services/LibVLCService.cs index 20a21c9..a3f248b 100644 --- a/CollimationCircles/Services/LibVLCService.cs +++ b/CollimationCircles/Services/LibVLCService.cs @@ -3,7 +3,6 @@ using CommunityToolkit.Mvvm.Messaging; using LibVLCSharp.Shared; using System.Collections.Generic; -using System.Threading; namespace CollimationCircles.Services { @@ -28,8 +27,7 @@ internal class LibVLCService : ILibVLCService public string FullAddress { get; set; } = string.Empty; public MediaPlayer MediaPlayer { get; } - - private StreamSource streamSource; + public StreamSource StreamSource { get; set; } public LibVLCService() { @@ -60,27 +58,11 @@ public LibVLCService() MediaPlayer.Stopped += (sender, e) => WeakReferenceMessenger.Default.Send(new CameraStateMessage(CameraState.Stopped)); } - public void Play() + public void Play(List controlsArgs) { - if (streamSource == StreamSource.RaspberryPi) - { - //rpicam-vid -t 0 --inline --listen -n -o tcp://0.0.0.0:5000 - - List parameters = [ - "-t", - "0", - "--inline", - "--listen", - "-n", - "-o", - $"tcp://0.0.0.0:{rpiPort}" - ]; - - AppService.ExecuteCommand( - "rpicam-vid", - parameters, timeout: 0); - - Thread.Sleep(1000); + if (StreamSource == StreamSource.RaspberryPi) + { + AppService.StartRaspberryPIStream(rpiPort, controlsArgs); } if (!string.IsNullOrWhiteSpace(FullAddress)) @@ -110,7 +92,7 @@ private string GetFullUrlFromParts() port = string.Empty; address = string.Empty; - if (streamSource == StreamSource.UVC) + if (StreamSource == StreamSource.UVC) { if (OperatingSystem.IsWindows()) { @@ -126,13 +108,13 @@ private string GetFullUrlFromParts() address = "/dev/video0"; } } - else if (streamSource == StreamSource.RaspberryPi) + else if (StreamSource == StreamSource.RaspberryPi) { protocol = "tcp/h264"; address = "localhost"; port = rpiPort; } - else if (streamSource == StreamSource.Remote) + else if (StreamSource == StreamSource.Remote) { protocol = "http"; } @@ -147,7 +129,7 @@ private string GetFullUrlFromParts() public string DefaultAddress(StreamSource streamSource) { - this.streamSource = streamSource; + StreamSource = streamSource; FullAddress = GetFullUrlFromParts(); return FullAddress; } diff --git a/CollimationCircles/ViewModels/CameraControlsViewModel.cs b/CollimationCircles/ViewModels/CameraControlsViewModel.cs index 49a41bf..b49f336 100644 --- a/CollimationCircles/ViewModels/CameraControlsViewModel.cs +++ b/CollimationCircles/ViewModels/CameraControlsViewModel.cs @@ -15,6 +15,7 @@ public partial class CameraControlsViewModel : BaseViewModel { private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); readonly ICameraControlService cameraControlService; + readonly ILibVLCService libVLCService; // user controls @@ -76,9 +77,10 @@ public partial class CameraControlsViewModel : BaseViewModel [ObservableProperty] private bool isOpened = true; - public CameraControlsViewModel(ICameraControlService cameraControlService) + public CameraControlsViewModel(ICameraControlService cameraControlService, ILibVLCService libVLCService) { this.cameraControlService = cameraControlService; + this.libVLCService = libVLCService; WeakReferenceMessenger.Default.Register(this, (r, m) => { @@ -115,7 +117,7 @@ protected override void OnPropertyChanged(PropertyChangedEventArgs e) if (double.TryParse(pVal, out double valDouble)) { - cameraControlService.Set(e.PropertyName, valDouble); + cameraControlService.Set(e.PropertyName, valDouble, libVLCService.StreamSource); } logger.Debug($"{e.PropertyName} changed to '{pVal}'"); diff --git a/CollimationCircles/ViewModels/StreamViewModel.cs b/CollimationCircles/ViewModels/StreamViewModel.cs index 226bc50..eb2f08f 100644 --- a/CollimationCircles/ViewModels/StreamViewModel.cs +++ b/CollimationCircles/ViewModels/StreamViewModel.cs @@ -116,7 +116,8 @@ private void PlayPause() { if (!libVLCService.MediaPlayer.IsPlaying) { - libVLCService.Play(); + var controls = CameraControlService.GetRaspberryPIControls(); + libVLCService.Play(controls); } else {