feat: CameraService camera image capture

Add GSS2.FrameDecoders project for image decoding realizations
Add IFrameDecoder interface and FrameDecoderHelper class
Add CameraService.CaptureImage method
This commit is contained in:
2025-12-18 09:26:00 +03:00
parent 650fd03bfe
commit 4de442f51f
6 changed files with 208 additions and 7 deletions
+3 -1
View File
@@ -17,13 +17,15 @@
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
<PackageReference Include="System.Device.Gpio" Version="4.0.1" />
<PackageReference Include="Iot.Device.Bindings" Version="4.0.1" />
<PackageReference Include="LibCameraSharp" Version="0.5.2-20251028" />
<PackageReference Include="LibCameraSharp" Version="0.5.2-20251214" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GSS2.LightsControl\CSharpClient\GSS2.LightsControl.CSharpClient.csproj" />
<ProjectReference Include="..\GSS2.FrameDecoders\GSS2.FrameDecoders.csproj" />
</ItemGroup>
</Project>
+96 -6
View File
@@ -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<OpenCvSharp.Mat?> 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<Memory<byte>>();
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<byte>(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;
}
}
}