Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Baballonia.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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.LibV4L2Capture", "src\Baballonia.LibV4L2Capture\Baballonia.LibV4L2Capture.csproj", "{02E9F6A2-A443-491D-93FF-6F002F3C494F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -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
{02E9F6A2-A443-491D-93FF-6F002F3C494F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02E9F6A2-A443-491D-93FF-6F002F3C494F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02E9F6A2-A443-491D-93FF-6F002F3C494F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02E9F6A2-A443-491D-93FF-6F002F3C494F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -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}
{02E9F6A2-A443-491D-93FF-6F002F3C494F} = {82C6A0B4-5014-4BE4-9F52-7DC1989134E3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8D6643ED-DDC1-4236-9471-024DCCFC81F6}
Expand Down
3 changes: 3 additions & 0 deletions src/Baballonia.Desktop/Baballonia.Desktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<PackageReference Include="Project-Babble.OpenCvSharp4.mini.runtime.ubuntu.22.04-arm64" Version="4.11.0.1" Condition="$([MSBuild]::IsOSPlatform('Linux')) And $(RuntimeIdentifier.Contains('arm'))" />
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.22.0" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="3.119.0" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<ProjectReference Include="..\Baballonia.LibV4L2Capture\Baballonia.LibV4L2Capture.csproj" Condition="$([MSBuild]::IsOSPlatform('Linux'))"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Baballonia.CaptureBin.IO\Baballonia.CaptureBin.IO.csproj" />
Expand Down Expand Up @@ -131,10 +132,12 @@

<Target Name="CopyModulesToFolder" AfterTargets="AfterBuild">
<ItemGroup>
<ModuleDlls Condition="Exists('$(OutputPath)Baballonia.LibV4L2Capture.dll')" Include="$(OutputPath)Baballonia.LibV4L2Capture.dll" />
<ModuleDlls Condition="Exists('$(OutputPath)Baballonia.SerialCameraCapture.dll')" Include="$(OutputPath)Baballonia.SerialCameraCapture.dll" />
<ModuleDlls Condition="Exists('$(OutputPath)Baballonia.OpenCVCapture.dll')" Include="$(OutputPath)Baballonia.OpenCVCapture.dll" />
<ModuleDlls Condition="Exists('$(OutputPath)Baballonia.IPCameraCapture.dll')" Include="$(OutputPath)Baballonia.IPCameraCapture.dll" />
<ModuleDlls Condition="Exists('$(OutputPath)Baballonia.VFTCapture.dll')" Include="$(OutputPath)Baballonia.VFTCapture.dll" />
<ModuleDlls Condition="Exists('$(OutputPath)Baballonia.LibV4L2Capture.pdb')" Include="$(OutputPath)Baballonia.LibV4L2Capture.pdb" />
<ModuleDlls Condition="Exists('$(OutputPath)Baballonia.SerialCameraCapture.pdb')" Include="$(OutputPath)Baballonia.SerialCameraCapture.pdb" />
<ModuleDlls Condition="Exists('$(OutputPath)Baballonia.OpenCVCapture.pdb')" Include="$(OutputPath)Baballonia.OpenCVCapture.pdb" />
<ModuleDlls Condition="Exists('$(OutputPath)Baballonia.IPCameraCapture.pdb')" Include="$(OutputPath)Baballonia.IPCameraCapture.pdb" />
Expand Down
14 changes: 14 additions & 0 deletions src/Baballonia.LibV4L2Capture/Baballonia.LibV4L2Capture.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Baballonia.SDK\Baballonia.SDK.csproj" />
</ItemGroup>

</Project>
115 changes: 115 additions & 0 deletions src/Baballonia.LibV4L2Capture/LibV4L2Capture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System.Runtime.InteropServices;
using Baballonia.LibV4L2Capture.V4L2;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using Capture = Baballonia.SDK.Capture;

namespace Baballonia.LibV4L2Capture;

public sealed class LibV4L2Capture(string source, ILogger<LibV4L2Capture> logger) : Capture(source, logger)
{
private Device? _device;
private CancellationTokenSource? _cts;
private Task? _captureTask;

public override Task<bool> StartCapture()
{
try
{
_device = Device.Connect(Source);

if (_device == null)
return Task.FromResult(false);

Logger.LogInformation($"Using pixel format: {_device.PixelFormat}");

_device.StartCapture();
IsReady = true;
}
catch (Exception e)
{
Logger.LogError(e.ToString());
return Task.FromResult(false);
}

_cts = new CancellationTokenSource();
var token = _cts.Token;

_captureTask = Task.Run(() => VideoCapture_UpdateLoop(token), token);

return Task.FromResult(true);
}

private void DecodeMJPEG(byte[] frame)
{
Mat mat = Cv2.ImDecode(frame, ImreadModes.Grayscale);
SetRawMat(mat);
}

private void DecodeYUYV(byte[] frame, uint width, uint height)
{
Mat yuyvMat = new Mat((int)height, (int)width, MatType.CV_8UC2);
Marshal.Copy(frame, 0, yuyvMat.Data, frame.Length);

Mat grayMat = new Mat();
Cv2.CvtColor(yuyvMat, grayMat, ColorConversionCodes.YUV2GRAY_YUY2);
SetRawMat(grayMat);
}

private async Task VideoCapture_UpdateLoop(CancellationToken ct)
{
while (!ct.IsCancellationRequested && _device != null)
{
try
{
if (_device.CaptureFrame(out byte[]? frame))
{
if (frame is { Length: > 0 })
{
switch (_device.PixelFormat)
{
case v4l2_pix_fmt.V4L2_PIX_FMT_MJPEG:
DecodeMJPEG(frame);
break;
case v4l2_pix_fmt.V4L2_PIX_FMT_YUYV:
var pix = _device.CurrentFormat.pix;
DecodeYUYV(frame, pix.width, pix.height);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
else
{
await Task.Delay(1, ct);
}
}
catch(Exception e)
{
SetRawMat(new Mat());
IsReady = false;
Logger.LogError(e.ToString());
_device.Dispose();
break;
}
}
}

public override Task<bool> StopCapture()
{
if (_device is null)
return Task.FromResult(false);

if (_captureTask != null)
{
_cts?.Cancel();
_captureTask.Wait();
}

IsReady = false;
_device?.Dispose();
_device = null;
return Task.FromResult(true);
}
}
29 changes: 29 additions & 0 deletions src/Baballonia.LibV4L2Capture/LibV4L2CaptureFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Baballonia.SDK;
using Microsoft.Extensions.Logging;

namespace Baballonia.LibV4L2Capture;

public class LibV4L2CaptureFactory : ICaptureFactory
{
private readonly ILoggerFactory _loggerFactory;

public LibV4L2CaptureFactory(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}

public Capture Create(string address)
{
return new LibV4L2Capture(address, _loggerFactory.CreateLogger<LibV4L2Capture>());
}

public bool CanConnect(string address)
{
return address.StartsWith("/dev/video");
}

public string GetProviderName()
{
return "Video4Linux2";
}
}
Loading