Skip to content

Commit

Permalink
Added support for camera controls for Raspberry Pi Camera stream
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonSander committed Feb 23, 2024
1 parent d4b360f commit 40ddd4d
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 45 deletions.
38 changes: 33 additions & 5 deletions CollimationCircles/Services/AppService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static string GetAppVersion()
var assemblyVersion = entryAssembly?.GetName().Version;

return assemblyVersion?.ToString() ?? "0.0.0";
}
}

public static T? Deserialize<T>(string jsonState)
{
Expand Down Expand Up @@ -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<string> arguments, Action? started = null, int timeout = -1)
{
Expand All @@ -195,7 +195,7 @@ public static async Task OpenFileBrowser(string path)
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
};

using Process process = new()
{
Expand Down Expand Up @@ -277,7 +277,7 @@ public static async Task OpenFileBrowser(string path)
}

return tcs.Task;
}
}

public static void OpenUrl(string url)
{
Expand All @@ -300,7 +300,7 @@ public static void OpenUrl(string url)
}

logger.Trace($"External url '{url}' opened");
}
}

public static string? GetLocalIPAddress()
{
Expand All @@ -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<string>? streamArgs = null)
{
//rpicam-vid -t 0 --inline --listen -n -o tcp://0.0.0.0:5000

ExecuteCommand("pkill", ["rpicam-vid"], timeout: 0);

List<string> 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);
}
}
92 changes: 84 additions & 8 deletions CollimationCircles/Services/CameraControlService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal class CameraControlService : ICameraControlService, IDisposable

private readonly object vc = new();

private readonly Dictionary<string, string> v4l2Properties = new()
private readonly Dictionary<string, string> v4l2controls = new()
{
{ "Brightness", "brightness" },
{ "Contrast", "contrast" },
Expand All @@ -28,7 +28,7 @@ internal class CameraControlService : ICameraControlService, IDisposable
{ "Zoom", "zoom_absolute" }
};

private readonly Dictionary<string, VideoCaptureProperties> OpenCVProperties = new()
private readonly Dictionary<string, VideoCaptureProperties> uvcControls = new()
{
{ "Brightness", VideoCaptureProperties.Brightness },
{ "Contrast", VideoCaptureProperties.Contrast },
Expand All @@ -46,21 +46,38 @@ internal class CameraControlService : ICameraControlService, IDisposable
{ "Zoom", VideoCaptureProperties.Zoom }
};

private static readonly Dictionary<string, string> 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())
{
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())
Expand All @@ -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}'");
}
}
Expand Down Expand Up @@ -106,5 +123,64 @@ public void Release()
((VideoCapture)vc)?.Release();
}
}

public static List<string> 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<string> 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;
}
}
}
3 changes: 2 additions & 1 deletion CollimationCircles/Services/ICameraControlService.cs
Original file line number Diff line number Diff line change
@@ -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();
}
Expand Down
3 changes: 2 additions & 1 deletion CollimationCircles/Services/ILibVLCService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> controlsArgs);
public string DefaultAddress(StreamSource streamSource);
}
}
36 changes: 9 additions & 27 deletions CollimationCircles/Services/LibVLCService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using CommunityToolkit.Mvvm.Messaging;
using LibVLCSharp.Shared;
using System.Collections.Generic;
using System.Threading;

namespace CollimationCircles.Services
{
Expand All @@ -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()
{
Expand Down Expand Up @@ -60,27 +58,11 @@ public LibVLCService()
MediaPlayer.Stopped += (sender, e) => WeakReferenceMessenger.Default.Send(new CameraStateMessage(CameraState.Stopped));
}

public void Play()
public void Play(List<string> controlsArgs)
{
if (streamSource == StreamSource.RaspberryPi)
{
//rpicam-vid -t 0 --inline --listen -n -o tcp://0.0.0.0:5000

List<string> 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))
Expand Down Expand Up @@ -110,7 +92,7 @@ private string GetFullUrlFromParts()
port = string.Empty;
address = string.Empty;

if (streamSource == StreamSource.UVC)
if (StreamSource == StreamSource.UVC)
{
if (OperatingSystem.IsWindows())
{
Expand All @@ -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";
}
Expand All @@ -147,7 +129,7 @@ private string GetFullUrlFromParts()

public string DefaultAddress(StreamSource streamSource)
{
this.streamSource = streamSource;
StreamSource = streamSource;
FullAddress = GetFullUrlFromParts();
return FullAddress;
}
Expand Down
6 changes: 4 additions & 2 deletions CollimationCircles/ViewModels/CameraControlsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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<CameraStateMessage>(this, (r, m) =>
{
Expand Down Expand Up @@ -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}'");
Expand Down
3 changes: 2 additions & 1 deletion CollimationCircles/ViewModels/StreamViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ private void PlayPause()
{
if (!libVLCService.MediaPlayer.IsPlaying)
{
libVLCService.Play();
var controls = CameraControlService.GetRaspberryPIControls();
libVLCService.Play(controls);
}
else
{
Expand Down

0 comments on commit 40ddd4d

Please sign in to comment.