forked from amkovkov/GranuSightSoftware2
feat: create camera service base
This commit is contained in:
@@ -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<CameraService> _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<CameraService> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user