feat: CameraService features and buffer mapping

Use cleanup method in dispose
Add initialization methods null fields check
Add image capture stream buffer memory mapping
This commit is contained in:
2025-12-17 09:48:50 +03:00
parent 34635c39b5
commit 650fd03bfe
+141 -58
View File
@@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using LibCameraSharp; using LibCameraSharp;
using Stream = LibCameraSharp.Stream; using Stream = LibCameraSharp.Stream;
@@ -6,6 +7,22 @@ namespace GSS2.Core.Hardware;
public class CameraService : IDisposable public class CameraService : IDisposable
{ {
private static class LibC
{
public const int PROT_READ = 0x01;
public const int MAP_SHARED = 0x0001;
[DllImport("libc", SetLastError = true)] public static extern IntPtr mmap(IntPtr address, ulong length, int prot, int flags, int fd, ulong offset);
[DllImport("libc", SetLastError = true)] public static extern int mumap(IntPtr address, ulong length);
}
private sealed class MappedBuffer
{
public int Fd { get; init; }
public IntPtr Address { get; init; }
public ulong Length { get; init; }
public ulong AlignedOffset { get; init; }
}
public class CameraServiceConfiguration public class CameraServiceConfiguration
{ {
public static CameraServiceConfiguration Default => new CameraServiceConfiguration public static CameraServiceConfiguration Default => new CameraServiceConfiguration
@@ -36,7 +53,8 @@ public class CameraService : IDisposable
CameraAcquired = 1 << 2, CameraAcquired = 1 << 2,
CameraStarted = 1 << 3, CameraStarted = 1 << 3,
ViewFinderFrameBufferAllocated = 1 << 4, ViewFinderFrameBufferAllocated = 1 << 4,
ImageCaptureFrameBufferAllocated = 1 << 5 ImageCaptureFrameBufferAllocated = 1 << 5,
ImageCaptureFrameBuffersMapped = 1 << 6
} }
private bool _disposedValue; private bool _disposedValue;
@@ -49,6 +67,7 @@ public class CameraService : IDisposable
private FrameBufferAllocator? _frameBufferAllocator; private FrameBufferAllocator? _frameBufferAllocator;
private Stream? _viewFinderStream; private Stream? _viewFinderStream;
private Stream? _imageCaptureStream; private Stream? _imageCaptureStream;
private List<MappedBuffer>? _mappedBuffers;
private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource(); private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
public CameraService(ILogger<CameraService> logger, CameraServiceConfiguration? configuration = null, bool autoInitialize = false) public CameraService(ILogger<CameraService> logger, CameraServiceConfiguration? configuration = null, bool autoInitialize = false)
@@ -98,11 +117,12 @@ public class CameraService : IDisposable
CameraConfigure(effectiveCancellationToken); CameraConfigure(effectiveCancellationToken);
CameraStart(effectiveCancellationToken); CameraStart(effectiveCancellationToken);
FrameBuffersAllocate(effectiveCancellationToken); FrameBuffersAllocate(effectiveCancellationToken);
ImageCaptureFrameBuffersMap(effectiveCancellationToken);
} }
catch (Exception e) catch (Exception e)
{ {
_logger.LogCritical(e, "An exception occurred while camera initialization"); _logger.LogCritical(e, "An exception occurred while camera initialization");
CleanupAfterFailedInitialization(); Cleanup();
throw; throw;
} }
} }
@@ -122,9 +142,12 @@ public class CameraService : IDisposable
} }
private void CameraSelect(CancellationToken cancellationToken) private void CameraSelect(CancellationToken cancellationToken)
{ {
if (_cameraManager is null)
throw new InvalidOperationException("CameraManager not initialized");
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
if (_cameraManager!.Cameras.Count() == 0) if (_cameraManager.Cameras.Count() == 0)
throw new Exception("No cameras found"); throw new Exception("No cameras found");
_camera = _cameraManager.Cameras _camera = _cameraManager.Cameras
@@ -143,9 +166,12 @@ public class CameraService : IDisposable
} }
private void CameraAcquire(CancellationToken cancellationToken) private void CameraAcquire(CancellationToken cancellationToken)
{ {
if (_camera is null)
throw new InvalidOperationException("Camera not initialized");
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
int result = _camera!.Acquire(); int result = _camera.Acquire();
if (result < 0) if (result < 0)
{ {
_logger.LogCritical("Error while camera acquire {Result}", result); _logger.LogCritical("Error while camera acquire {Result}", result);
@@ -156,9 +182,12 @@ public class CameraService : IDisposable
} }
private void CameraConfigure(CancellationToken cancellationToken) private void CameraConfigure(CancellationToken cancellationToken)
{ {
if (_camera is null)
throw new InvalidOperationException("Camera not initialized");
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
_cameraConfiguration = _camera!.GenerateConfiguration( _cameraConfiguration = _camera.GenerateConfiguration(
StreamRoleEnum.Viewfinder, StreamRoleEnum.Viewfinder,
StreamRoleEnum.StillCapture); StreamRoleEnum.StillCapture);
@@ -188,9 +217,12 @@ public class CameraService : IDisposable
} }
private void CameraStart(CancellationToken cancellationToken) private void CameraStart(CancellationToken cancellationToken)
{ {
if (_camera is null)
throw new InvalidOperationException("Camera not initialized");
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
int result = _camera!.Start(); int result = _camera.Start();
if (result < 0) if (result < 0)
{ {
_logger.LogCritical("Error while camera start {Result}", result); _logger.LogCritical("Error while camera start {Result}", result);
@@ -201,13 +233,21 @@ public class CameraService : IDisposable
} }
private void FrameBuffersAllocate(CancellationToken cancellationToken) private void FrameBuffersAllocate(CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); if (_camera is null)
throw new InvalidOperationException("Camera not initialized");
_frameBufferAllocator = new FrameBufferAllocator(_camera!); if (_viewFinderStream is null)
throw new InvalidOperationException("ViewFinderStream not initialized");
if (_imageCaptureStream is null)
throw new InvalidOperationException("ImageCaptureStream not initialized");
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
int result = _frameBufferAllocator.Allocate(_viewFinderStream!); _frameBufferAllocator = new FrameBufferAllocator(_camera);
cancellationToken.ThrowIfCancellationRequested();
int result = _frameBufferAllocator.Allocate(_viewFinderStream);
if (result < 0) if (result < 0)
{ {
_logger.LogCritical("Error while view finder frame buffers allocation {Result}", result); _logger.LogCritical("Error while view finder frame buffers allocation {Result}", result);
@@ -218,7 +258,7 @@ public class CameraService : IDisposable
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
result = _frameBufferAllocator.Allocate(_imageCaptureStream!); result = _frameBufferAllocator.Allocate(_imageCaptureStream);
if (result < 0) if (result < 0)
{ {
_logger.LogCritical("Error while image capture frame buffers allocation {Result}", result); _logger.LogCritical("Error while image capture frame buffers allocation {Result}", result);
@@ -227,10 +267,96 @@ public class CameraService : IDisposable
_state |= State.ImageCaptureFrameBufferAllocated; _state |= State.ImageCaptureFrameBufferAllocated;
} }
private void CleanupAfterFailedInitialization() 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");
cancellationToken.ThrowIfCancellationRequested();
_mappedBuffers = new List<MappedBuffer>();
var frameBuffers = _frameBufferAllocator.Buffers(_imageCaptureStream);
foreach (var buffer in frameBuffers)
{
cancellationToken.ThrowIfCancellationRequested();
var planesByFd = buffer.Planes.GroupBy(p => p.Fd);
foreach (var fdGroup in planesByFd)
{
cancellationToken.ThrowIfCancellationRequested();
int fd = fdGroup.Key.Get();
ulong minOffset = fdGroup.Min(p => p.Offset);
ulong maxEnd = fdGroup.Max(p => p.Offset + p.Length);
ulong pageSize = (ulong)Environment.SystemPageSize;
ulong alignedOffset = minOffset & ~(pageSize - 1);
ulong alignedLength = maxEnd - alignedOffset;
IntPtr address = LibC.mmap(IntPtr.Zero, alignedLength, LibC.PROT_READ, LibC.MAP_SHARED, fd, alignedOffset);
if (address == new IntPtr(-1))
{
int error = Marshal.GetLastWin32Error();
throw new InvalidOperationException($"mmap failed for fd={fd}, error={error}");
}
_mappedBuffers.Add(new MappedBuffer
{
Fd = fd,
Address = address,
Length = alignedLength,
AlignedOffset = alignedOffset
});
}
}
_state |= State.ImageCaptureFrameBuffersMapped;
}
private IntPtr GetPlanePointer(FrameBuffer.Plane plane)
{
if (_mappedBuffers is null)
throw new InvalidOperationException("Buffers not mapped");
var mapped = _mappedBuffers.First(m => m.Fd == plane.Fd.Get());
ulong offsetInMapping = plane.Offset - mapped.AlignedOffset;
return IntPtr.Add(mapped.Address, checked((int)offsetInMapping));
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_disposeCts.Cancel();
Cleanup();
}
_disposedValue = true;
}
}
void IDisposable.Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Cleanup()
{ {
try try
{ {
if (_state.HasFlag(State.ImageCaptureFrameBuffersMapped) && _mappedBuffers is not null)
{
foreach (var buffer in _mappedBuffers)
LibC.mumap(buffer.Address, buffer.Length);
_mappedBuffers.Clear();
}
if (_state.HasFlag(State.ViewFinderFrameBufferAllocated) && _viewFinderStream is not null) if (_state.HasFlag(State.ViewFinderFrameBufferAllocated) && _viewFinderStream is not null)
_frameBufferAllocator?.Free(_viewFinderStream); _frameBufferAllocator?.Free(_viewFinderStream);
if (_state.HasFlag(State.ImageCaptureFrameBufferAllocated) && _imageCaptureStream is not null) if (_state.HasFlag(State.ImageCaptureFrameBufferAllocated) && _imageCaptureStream is not null)
@@ -242,10 +368,10 @@ public class CameraService : IDisposable
if (_state.HasFlag(State.CameraManagerStarted)) if (_state.HasFlag(State.CameraManagerStarted))
_cameraManager?.Stop(); _cameraManager?.Stop();
_frameBufferAllocator?.Dispose();
_cameraManager?.Dispose();
_viewFinderStream?.Dispose(); _viewFinderStream?.Dispose();
_imageCaptureStream?.Dispose(); _imageCaptureStream?.Dispose();
_frameBufferAllocator?.Dispose();
_cameraManager?.Dispose();
} }
catch catch
{ } { }
@@ -256,52 +382,9 @@ public class CameraService : IDisposable
_frameBufferAllocator = null; _frameBufferAllocator = null;
_viewFinderStream = null; _viewFinderStream = null;
_imageCaptureStream = null; _imageCaptureStream = null;
_mappedBuffers = null;
_state = State.NotInitialized; _state = State.NotInitialized;
} }
} }
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_disposeCts.Cancel();
if (_state.HasFlag(State.ViewFinderFrameBufferAllocated) && _viewFinderStream is not null)
_frameBufferAllocator?.Free(_viewFinderStream);
if (_state.HasFlag(State.ImageCaptureFrameBufferAllocated) && _imageCaptureStream is not null)
_frameBufferAllocator?.Free(_imageCaptureStream);
if (_state.HasFlag(State.CameraStarted))
_camera?.Stop();
if (_state.HasFlag(State.CameraAcquired))
_camera?.Release();
if (_state.HasFlag(State.CameraManagerStarted))
_cameraManager?.Stop();
_viewFinderStream?.Dispose();
_imageCaptureStream?.Dispose();
_frameBufferAllocator?.Dispose();
_cameraConfiguration?.Dispose();
_cameraManager?.Dispose();
_cameraManager = null;
_frameBufferAllocator = null;
_camera = null;
_cameraConfiguration = null;
_frameBufferAllocator = null;
_viewFinderStream = null;
_imageCaptureStream = null;
}
_disposedValue = true;
}
}
void IDisposable.Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
} }