diff --git a/Baballonia.sln b/Baballonia.sln
index eb8a3ad8..c897b3da 100644
--- a/Baballonia.sln
+++ b/Baballonia.sln
@@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Baballonia.FastCorruptionDe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Baballonia.CaptureBin.IO", "src\Baballonia.CaptureBin.IO\Baballonia.CaptureBin.IO.csproj", "{8D298C1C-774E-4BF0-A107-D5D3985C7A9B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Baballonia.LibUVCCapture", "src\Baballonia.LibUVCCapture\Baballonia.LibUVCCapture.csproj", "{03715536-9789-4282-82A1-02693A5FE696}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -120,6 +122,10 @@ Global
{8D298C1C-774E-4BF0-A107-D5D3985C7A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D298C1C-774E-4BF0-A107-D5D3985C7A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D298C1C-774E-4BF0-A107-D5D3985C7A9B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {03715536-9789-4282-82A1-02693A5FE696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {03715536-9789-4282-82A1-02693A5FE696}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03715536-9789-4282-82A1-02693A5FE696}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {03715536-9789-4282-82A1-02693A5FE696}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -135,6 +141,7 @@ Global
{575BB586-5394-4F94-A178-DF4062EC44A1} = {82C6A0B4-5014-4BE4-9F52-7DC1989134E3}
{4B08BDE8-099A-405F-AB71-094E8FC24AB1} = {82C6A0B4-5014-4BE4-9F52-7DC1989134E3}
{8D298C1C-774E-4BF0-A107-D5D3985C7A9B} = {82C6A0B4-5014-4BE4-9F52-7DC1989134E3}
+ {03715536-9789-4282-82A1-02693A5FE696} = {82C6A0B4-5014-4BE4-9F52-7DC1989134E3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8D6643ED-DDC1-4236-9471-024DCCFC81F6}
diff --git a/src/Baballonia.Desktop/Baballonia.Desktop.csproj b/src/Baballonia.Desktop/Baballonia.Desktop.csproj
index 70d9febf..67c0a190 100644
--- a/src/Baballonia.Desktop/Baballonia.Desktop.csproj
+++ b/src/Baballonia.Desktop/Baballonia.Desktop.csproj
@@ -45,6 +45,7 @@
+
@@ -53,6 +54,7 @@
+
@@ -135,10 +137,12 @@
+
+
@@ -149,6 +153,7 @@
+
diff --git a/src/Baballonia.LibUVCCapture/Baballonia.LibUVCCapture.csproj b/src/Baballonia.LibUVCCapture/Baballonia.LibUVCCapture.csproj
new file mode 100644
index 00000000..1317b3c3
--- /dev/null
+++ b/src/Baballonia.LibUVCCapture/Baballonia.LibUVCCapture.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ disable
+
+
+
+
+
+
+
+
diff --git a/src/Baballonia.LibUVCCapture/LibUVCCapture.cs b/src/Baballonia.LibUVCCapture/LibUVCCapture.cs
new file mode 100644
index 00000000..993c91d5
--- /dev/null
+++ b/src/Baballonia.LibUVCCapture/LibUVCCapture.cs
@@ -0,0 +1,126 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Baballonia.SDK;
+using Microsoft.Extensions.Logging;
+using OpenCvSharp;
+using Uvc.Net;
+
+namespace Baballonia.LibUVCCapture;
+
+public sealed class LibUVCCapture(string source, ILogger logger) : Capture(source, logger)
+{
+ private Context _context;
+ private Device _device;
+ private DeviceHandle _deviceHandle;
+ private bool _connected;
+
+ public override Task StartCapture()
+ {
+ _context = new Context();
+ _device = FindDeviceByPath(Source, _context);
+ if (_device is null)
+ {
+ _context.Dispose();
+ _context = null;
+ return Task.FromResult(false);
+ }
+ return Task.Run(() =>
+ {
+ var open = _device.Open();
+ try
+ {
+ var formats = open.GetStreamControlFormats().ToArray().OrderBy(i => i.Width).ToList();
+ var formatIndex = formats.FindIndex(i => i is { Width: > 256, Height: > 256, Format: FrameFormat.Mjpeg });
+ if (formatIndex == -1)
+ {
+ Logger.LogInformation("Couldn't find format index");
+ open.Dispose();
+ _device.Dispose();
+ _device = null;
+ _context.Dispose();
+ _context = null;
+ return false;
+ }
+ Logger.LogInformation("Found format index");
+ var format = formats[formatIndex];
+ var control = open.GetStreamControlFormatSize(format.Format, format.Width, format.Height, 0);
+ try
+ {
+ Logger.LogInformation("Starting stream");
+ open.StartStreaming(ref control, Callback);
+ Logger.LogInformation("Started stream");
+ _deviceHandle = open;
+ _connected = true;
+ Logger.LogInformation("Blah");
+ return true;
+ }
+ catch
+ {
+ open.StopStreaming();
+
+ open.Dispose();
+ _device.Dispose();
+ _device = null;
+ _context.Dispose();
+ _context = null;
+ return false;
+ }
+ }
+ catch
+ {
+ open.Dispose();
+ _device?.Dispose();
+ _device = null;
+ _context?.Dispose();
+ _context = null;
+ return false;
+ }
+ });
+ }
+
+ public override Task StopCapture()
+ {
+ _connected = false;
+ if (_deviceHandle is null) return Task.FromResult(true);
+
+ _deviceHandle.StopStreaming();
+ _deviceHandle.Dispose();
+ _deviceHandle = null;
+
+ _context.Dispose();
+ _context = null;
+
+ return Task.FromResult(true);
+ }
+
+ private void Callback(ref Frame frame, IntPtr userPtr)
+ {
+ Logger.LogInformation("Callback called");
+ if (!_connected) return;
+ Logger.LogInformation("IsConnected");
+ if (frame.FrameFormat is not FrameFormat.Mjpeg) return;
+ Logger.LogInformation("CorrectFormat");
+ var data = frame.GetData();
+ if (data.Length == 0) return;
+ Logger.LogInformation("HasData");
+ SetRawMat(Mat.FromImageData(data));
+ }
+
+ public static Device FindDeviceByPath(string path, Context context)
+ {
+ if (!path.Contains("/dev/video")) return null;
+ var videoIndex = path.Replace("/dev/", "");
+ var ueventFilePath = $"/sys/class/video4linux/{videoIndex}/device/uevent";
+ if (!File.Exists(ueventFilePath)) return null;
+ var ueventText = File.ReadAllLines(ueventFilePath);
+ var line = ueventText.FirstOrDefault(i => i.StartsWith("PRODUCT="));
+ if (line is null) return null;
+ var numbers = line.Replace("PRODUCT=", "").Split('/').Select(i => Convert.ToUInt16(i, 16)).ToArray();
+ if (numbers.Length < 2) return null;
+ var vendor = numbers[0];
+ var product = numbers[1];
+ return context.FindDevice(vendor, product);
+ }
+}
diff --git a/src/Baballonia.LibUVCCapture/LibUVCCaptureFactory.cs b/src/Baballonia.LibUVCCapture/LibUVCCaptureFactory.cs
new file mode 100644
index 00000000..44b109b2
--- /dev/null
+++ b/src/Baballonia.LibUVCCapture/LibUVCCaptureFactory.cs
@@ -0,0 +1,18 @@
+using System;
+using Baballonia.SDK;
+using Microsoft.Extensions.Logging;
+
+namespace Baballonia.LibUVCCapture;
+
+public class LibUVCCaptureFactory : ICaptureFactory
+{
+ private readonly ILoggerFactory _loggerFactory;
+
+ public LibUVCCaptureFactory(ILoggerFactory loggerFactory) => _loggerFactory = loggerFactory;
+
+ public Capture Create(string address) => new LibUVCCapture(address, _loggerFactory.CreateLogger());
+
+ public bool CanConnect(string address) => address.StartsWith("/dev/video");
+
+ public string GetProviderName() => nameof(LibUVCCapture);
+}