diff --git a/GSS2.Core/GSS2.Core.csproj b/GSS2.Core/GSS2.Core.csproj
index ccefd78..affbaaa 100644
--- a/GSS2.Core/GSS2.Core.csproj
+++ b/GSS2.Core/GSS2.Core.csproj
@@ -17,13 +17,15 @@
+
-
+
+
diff --git a/GSS2.Core/Hardware/CameraService.cs b/GSS2.Core/Hardware/CameraService.cs
index 80ebfd7..908eb4b 100644
--- a/GSS2.Core/Hardware/CameraService.cs
+++ b/GSS2.Core/Hardware/CameraService.cs
@@ -269,10 +269,10 @@ public class CameraService : IDisposable
}
private void ImageCaptureFrameBuffersMap(CancellationToken cancellationToken)
{
- if (_frameBufferAllocator is null)
- throw new InvalidOperationException("FrameBufferAllocator not initialized");
if (_imageCaptureStream is null)
throw new InvalidOperationException("ImageCaptureStream not initialized");
+ if (_frameBufferAllocator is null)
+ throw new InvalidOperationException("FrameBufferAllocator not initialized");
cancellationToken.ThrowIfCancellationRequested();
@@ -304,7 +304,7 @@ public class CameraService : IDisposable
if (address == new IntPtr(-1))
{
int error = Marshal.GetLastWin32Error();
- throw new InvalidOperationException($"mmap failed for fd={fd}, error={error}");
+ throw new InvalidOperationException($"Error while buffers mapping, mmap failed for fd={fd}, error={error}");
}
_mappedBuffers.Add(new MappedBuffer
@@ -320,6 +320,95 @@ public class CameraService : IDisposable
_state |= State.ImageCaptureFrameBuffersMapped;
}
+ public async Task CaptureImage(CancellationToken cancellationToken)
+ {
+ if (_camera is null)
+ throw new InvalidOperationException("Camera not initialized");
+ if (_imageCaptureStream is null)
+ throw new InvalidOperationException("ImageCaptureStream not initialized");
+ if (_frameBufferAllocator is null)
+ throw new InvalidOperationException("FrameBufferAllocator not initialized");
+ if (_mappedBuffers is null)
+ throw new InvalidOperationException("Buffers not mapped");
+
+ var effectiveCancellationToken = CancellationTokenSource
+ .CreateLinkedTokenSource(_disposeCts.Token, cancellationToken)
+ .Token;
+
+ OpenCvSharp.Mat? image = null;
+
+ try
+ {
+ effectiveCancellationToken.ThrowIfCancellationRequested();
+
+ var buffers = _frameBufferAllocator.Buffers(_imageCaptureStream);
+ var buffer = buffers.Where(b => b.Metadata.Status == FrameMetadata.StatusEnum.FrameStartup || b.Metadata.Status == FrameMetadata.StatusEnum.FrameSuccess).First();
+ var request = _camera.CreateRequest(checked((ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()));
+ var result = request.AddBuffer(_imageCaptureStream, buffer);
+
+ if (result < 0)
+ throw new Exception("Error while request create");
+
+ effectiveCancellationToken.ThrowIfCancellationRequested();
+
+ var cts = new TaskCompletionSource();
+ void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e)
+ {
+ if (e.Request.Handle == request.Handle)
+ cts.SetResult();
+ };
+
+ _camera.RequestCompleted += RequestCompleted;
+ await cts.Task.WaitAsync(effectiveCancellationToken);
+ _camera.RequestCompleted -= RequestCompleted;
+
+ var planesData = new List>();
+ foreach (var plane in buffer.Planes)
+ {
+ effectiveCancellationToken.ThrowIfCancellationRequested();
+
+ var pointer = GetPlanePointer(plane);
+ var data = new byte[plane.Length];
+ Marshal.Copy(pointer, data, 0, checked((int)plane.Length));
+ planesData.Add(new Memory(data));
+ }
+
+ effectiveCancellationToken.ThrowIfCancellationRequested();
+
+ var config = _imageCaptureStream.Configuration();
+ var size = config.Size;
+ var fourcc = config.PixelFormat.Fourcc;
+ var modifier = config.PixelFormat.Modifier;
+ var primariesEnum = config.ColorSpace!.Primaries;
+ var transferFunctionEnum = config.ColorSpace.TransferFunction;
+ var ycbcrEncodingEnum = config.ColorSpace.YcbcrEncoding;
+ var rangeEnum = config.ColorSpace.Range;
+ var stride = config.Stride;
+
+ effectiveCancellationToken.ThrowIfCancellationRequested();
+
+ var decoder = FrameDecoderHelper.FindDecoder(fourcc, modifier, primariesEnum, transferFunctionEnum, ycbcrEncodingEnum, rangeEnum);
+ if (decoder is null)
+ throw new Exception($"Cannot find decoder for fourcc={fourcc} modifier={modifier} primariesEnum={primariesEnum} transferFunctionEnum={transferFunctionEnum} ycbcrEncodingEnum={ycbcrEncodingEnum} rangeEnum={rangeEnum}");
+
+ effectiveCancellationToken.ThrowIfCancellationRequested();
+
+ try
+ {
+ image = decoder.Decode(checked((int)size.Width), checked((int)size.Height), checked((int)stride), planesData);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Error while image decode");
+ throw;
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Error while image capture");
+ }
+ return image;
+ }
private IntPtr GetPlanePointer(FrameBuffer.Plane plane)
{
if (_mappedBuffers is null)
@@ -373,8 +462,10 @@ public class CameraService : IDisposable
_frameBufferAllocator?.Dispose();
_cameraManager?.Dispose();
}
- catch
- { }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Error while CameraService cleanup");
+ }
finally
{
_cameraManager = null;
@@ -386,5 +477,4 @@ public class CameraService : IDisposable
_state = State.NotInitialized;
}
}
-
}
diff --git a/GSS2.FrameDecoders/FrameDecoderHelper.cs b/GSS2.FrameDecoders/FrameDecoderHelper.cs
new file mode 100644
index 0000000..81d759f
--- /dev/null
+++ b/GSS2.FrameDecoders/FrameDecoderHelper.cs
@@ -0,0 +1,63 @@
+using GSS2.FrameDecoders;
+
+using LibCameraSharp;
+
+public static class FrameDecoderHelper
+{
+ private static readonly IReadOnlyList _decoders;
+
+ static FrameDecoderHelper()
+ {
+ _decoders = LoadDecoders();
+ }
+
+ private static IReadOnlyList LoadDecoders()
+ {
+ var decoderInterface = typeof(IFrameDecoder);
+ var assembly = decoderInterface.Assembly;
+
+ var decoders = assembly
+ .GetTypes()
+ .Where(t =>
+ !t.IsAbstract &&
+ !t.IsInterface &&
+ decoderInterface.IsAssignableFrom(t))
+ .Select(CreateDecoderInstance)
+ .Where(d => d is not null)
+ .Cast()
+ .ToList();
+
+ return decoders;
+ }
+ private static IFrameDecoder? CreateDecoderInstance(Type type)
+ {
+ if (type.GetConstructor(Type.EmptyTypes) is null)
+ return null;
+
+ try
+ {
+ return (IFrameDecoder?)Activator.CreateInstance(type);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ public static IFrameDecoder? FindDecoder(
+ uint fourcc,
+ ulong modifier,
+ ColorSpace.PrimariesEnum primariesEnum,
+ ColorSpace.TransferFunctionEnum transferFunctionEnum,
+ ColorSpace.YcbcrEncodingEnum ycbcrEncodingEnum,
+ ColorSpace.RangeEnum rangeEnum)
+ {
+ return _decoders.FirstOrDefault(d =>
+ d.Fourcc == fourcc &&
+ d.Modifier == modifier &&
+ d.PrimariesEnum == primariesEnum &&
+ d.TransferFunctionEnum == transferFunctionEnum &&
+ d.YcbcrEncodingEnum == ycbcrEncodingEnum &&
+ d.RangeEnum == rangeEnum);
+ }
+}
\ No newline at end of file
diff --git a/GSS2.FrameDecoders/GSS2.FrameDecoders.csproj b/GSS2.FrameDecoders/GSS2.FrameDecoders.csproj
new file mode 100644
index 0000000..05b6437
--- /dev/null
+++ b/GSS2.FrameDecoders/GSS2.FrameDecoders.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/GSS2.FrameDecoders/IFrameDecoder.cs b/GSS2.FrameDecoders/IFrameDecoder.cs
new file mode 100644
index 0000000..88012b1
--- /dev/null
+++ b/GSS2.FrameDecoders/IFrameDecoder.cs
@@ -0,0 +1,17 @@
+using LibCameraSharp;
+
+using OpenCvSharp;
+
+namespace GSS2.FrameDecoders;
+
+public interface IFrameDecoder
+{
+ public uint Fourcc { get; }
+ public ulong Modifier { get; }
+ public ColorSpace.PrimariesEnum PrimariesEnum { get; }
+ public ColorSpace.TransferFunctionEnum TransferFunctionEnum { get; }
+ public ColorSpace.YcbcrEncodingEnum YcbcrEncodingEnum { get; }
+ public ColorSpace.RangeEnum RangeEnum { get; }
+
+ public Mat Decode(int width, int height, int stride, List> planes);
+}
diff --git a/GranuSightSoftware2.sln b/GranuSightSoftware2.sln
index d2a5f97..24bf55b 100644
--- a/GranuSightSoftware2.sln
+++ b/GranuSightSoftware2.sln
@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CSharpClient", "CSharpClien
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.LightsControl.CSharpClient", "GSS2.LightsControl\CSharpClient\GSS2.LightsControl.CSharpClient.csproj", "{D0130B90-6ECA-49CA-A126-EFEFF481FC74}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.FrameDecoders", "GSS2.FrameDecoders\GSS2.FrameDecoders.csproj", "{05085293-E710-41D6-8F61-701ADBA52FA9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -59,6 +61,18 @@ Global
{D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x64.Build.0 = Release|Any CPU
{D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x86.ActiveCfg = Release|Any CPU
{D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x86.Build.0 = Release|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|x64.Build.0 = Debug|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Debug|x86.Build.0 = Debug|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x64.ActiveCfg = Release|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x64.Build.0 = Release|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x86.ActiveCfg = Release|Any CPU
+ {05085293-E710-41D6-8F61-701ADBA52FA9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE