From 5a4ef0b4f71d87cab1c76a2095782e3e96f3a653 Mon Sep 17 00:00:00 2001 From: Alek-ban Date: Mon, 15 Dec 2025 15:32:14 +0300 Subject: [PATCH] feat: create camera service base --- GSS2.Core/Hardware/CameraService.cs | 307 ++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) diff --git a/GSS2.Core/Hardware/CameraService.cs b/GSS2.Core/Hardware/CameraService.cs index e69de29..3ec4ad2 100644 --- a/GSS2.Core/Hardware/CameraService.cs +++ b/GSS2.Core/Hardware/CameraService.cs @@ -0,0 +1,307 @@ +using Microsoft.Extensions.Logging; +using LibCameraSharp; +using Stream = LibCameraSharp.Stream; + +namespace GSS2.Core.Hardware; + +public class CameraService : IDisposable +{ + public class CameraServiceConfiguration + { + public static CameraServiceConfiguration Default => new CameraServiceConfiguration + { + CameraId = "", + FrameBufferCount = 5, + ViewFinderWidth = 1920, + ViewFinderHeight = 1080, + ViewFinderFps = 30, + ImageCaptureWidth = 4056, + ImageCaptureHeight = 3080, + }; + + public string CameraId { get; set; } = ""; + public int FrameBufferCount { get; set; } + public int ViewFinderWidth { get; set; } + public int ViewFinderHeight { get; set; } + public int ViewFinderFps { get; set; } + public int ImageCaptureWidth { get; set; } + public int ImageCaptureHeight { get; set; } + } + + [Flags] + private enum State + { + NotInitialized = 0, + CameraManagerStarted = 1 << 1, + CameraAcquired = 1 << 2, + CameraStarted = 1 << 3, + ViewFinderFrameBufferAllocated = 1 << 4, + ImageCaptureFrameBufferAllocated = 1 << 5 + } + + private bool _disposedValue; + private readonly ILogger _logger; + private readonly CameraServiceConfiguration _configuration; + private State _state = State.NotInitialized; + private CameraManager? _cameraManager; + private Camera? _camera; + private CameraConfiguration? _cameraConfiguration; + private FrameBufferAllocator? _frameBufferAllocator; + private Stream? _viewFinderStream; + private Stream? _imageCaptureStream; + private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource(); + + public CameraService(ILogger logger, CameraServiceConfiguration? configuration = null, bool autoInitialize = false) + { + _logger = logger; + + _logger.LogInformation("CameraService initialization"); + + if (configuration is null) + { + _logger.LogWarning("CameraService configuration not set. Using default configuration."); + _configuration = CameraServiceConfiguration.Default; + } + else + { + _configuration = configuration; + } + + _logger.LogDebug(@$"CameraService use following configuration: + "); + + if (autoInitialize) + Initialize(CancellationToken.None); + } + + public async Task InitializeAsync(CancellationToken cancellationToken) + { + var linkedCancellationToken = CancellationTokenSource + .CreateLinkedTokenSource(_disposeCts.Token, cancellationToken) + .Token; + await Task.Run(() => Initialize(linkedCancellationToken), linkedCancellationToken); + } + public void Initialize(CancellationToken cancellationToken) + { + var effectiveCancellationToken = CancellationTokenSource + .CreateLinkedTokenSource(_disposeCts.Token, cancellationToken) + .Token; + + try + { + if (_state != State.NotInitialized) + throw new InvalidOperationException("Camera already initialized"); + + CameraManagerCreateAndStart(effectiveCancellationToken); + CameraSelect(effectiveCancellationToken); + CameraAcquire(effectiveCancellationToken); + CameraConfigure(effectiveCancellationToken); + CameraStart(effectiveCancellationToken); + FrameBuffersAllocate(effectiveCancellationToken); + } + catch (Exception e) + { + _logger.LogCritical(e, "An exception occurred while camera initialization"); + CleanupAfterFailedInitialization(); + throw; + } + } + private void CameraManagerCreateAndStart(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _cameraManager = new CameraManager(); + int result = _cameraManager.Start(); + if (result < 0) + { + _logger.LogCritical("Error while camera manager start {Result}", result); + throw new Exception($"Error while camera manager start {result}"); + } + + _state |= State.CameraManagerStarted; + } + private void CameraSelect(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_cameraManager!.Cameras.Count() == 0) + throw new Exception("No cameras found"); + + _camera = _cameraManager.Cameras + .FirstOrDefault(c => c.Id == _configuration.CameraId); + + if (_camera is null) + { + var camerasIds = string.Join("\", \"", _cameraManager.Cameras.Select(c => c.Id)); + _logger.LogError( + "Configured camera id (\"{CameraId}\") not found, using first camera from following list: [\"{Cameras}\"]", + _configuration.CameraId, + camerasIds); + + _camera = _cameraManager.Cameras.First(); + } + } + private void CameraAcquire(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + int result = _camera!.Acquire(); + if (result < 0) + { + _logger.LogCritical("Error while camera acquire {Result}", result); + throw new Exception($"Error while camera acquire {result}"); + } + + _state |= State.CameraAcquired; + } + private void CameraConfigure(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _cameraConfiguration = _camera!.GenerateConfiguration( + StreamRoleEnum.Viewfinder, + StreamRoleEnum.StillCapture); + + var viewFinderConfig = _cameraConfiguration.First(); + viewFinderConfig.BufferCount = (uint)_configuration.FrameBufferCount; + viewFinderConfig.Size = new Size( + (uint)_configuration.ViewFinderWidth, + (uint)_configuration.ViewFinderHeight); + + if (_cameraConfiguration.Validate() == CameraConfiguration.Status.Invalid) + { + _logger.LogCritical("Error while camera configuration validation"); + throw new Exception("Error while camera configuration validation"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + int result = _camera.Configure(_cameraConfiguration); + if (result < 0) + { + _logger.LogCritical("Error while camera configuration {Result}", result); + throw new Exception($"Error while camera configuration {result}"); + } + + _viewFinderStream = _cameraConfiguration.First().Stream(); + _imageCaptureStream = _cameraConfiguration.Skip(1).First().Stream(); + } + private void CameraStart(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + int result = _camera!.Start(); + if (result < 0) + { + _logger.LogCritical("Error while camera start {Result}", result); + throw new Exception($"Error while camera start {result}"); + } + + _state |= State.CameraStarted; + } + private void FrameBuffersAllocate(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _frameBufferAllocator = new FrameBufferAllocator(_camera!); + + cancellationToken.ThrowIfCancellationRequested(); + + int result = _frameBufferAllocator.Allocate(_viewFinderStream!); + if (result < 0) + { + _logger.LogCritical("Error while view finder frame buffers allocation {Result}", result); + throw new Exception($"Error while view finder frame buffers allocation {result}"); + } + + _state |= State.ViewFinderFrameBufferAllocated; + + cancellationToken.ThrowIfCancellationRequested(); + + result = _frameBufferAllocator.Allocate(_imageCaptureStream!); + if (result < 0) + { + _logger.LogCritical("Error while image capture frame buffers allocation {Result}", result); + throw new Exception($"Error while image capture frame buffers allocation {result}"); + } + + _state |= State.ImageCaptureFrameBufferAllocated; + } + private void CleanupAfterFailedInitialization() + { + try + { + 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(); + + _frameBufferAllocator?.Dispose(); + _cameraManager?.Dispose(); + _viewFinderStream?.Dispose(); + _imageCaptureStream?.Dispose(); + } + catch + { } + finally + { + _cameraManager = null; + _camera = null; + _frameBufferAllocator = null; + _viewFinderStream = null; + _imageCaptureStream = null; + _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); + } +}