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); +}