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); } }