using System.Buffers; using System.Text; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; using LibCameraSharp; using GSS2.Core.Extensions; using GSS2.Core.Abstractions; using GSS2.Core.Helpers; using Stream = LibCameraSharp.Stream; namespace GSS2.Core.Hardware; public class CameraService : IDisposable { public 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 munmap(IntPtr address, ulong length); [DllImport("libc", SetLastError = true)] public static extern int close(int fd); [DllImport("libc", SetLastError = true)] public static extern int memfd_create(string name, uint flags); } private sealed class MappedBuffer { public int Fd { get; init; } public IntPtr Address { get; init; } public ulong Length { get; init; } public ulong AlignedOffset { get; init; } } [Flags] private enum State { NotInitialized = 0, CameraManagerStarted = 1 << 1, CameraAcquired = 1 << 2, CameraStarted = 1 << 3, ImageCaptureFrameBufferAllocated = 1 << 5, ImageCaptureFrameBuffersMapped = 1 << 6 } public record CameraServiceConfiguration : ServiceConfigurationBase { public static CameraServiceConfiguration Default => new CameraServiceConfiguration { CameraId = "", ImageCaptureFrameBufferCount = 2, ImageCaptureWidth = 4056, ImageCaptureHeight = 3080, CameraHardSettings = null, DecodeGainR = 1, DecodeGainG = 1, DecodeGainB = 1, DecodeBlackLevel = 256 }; public string CameraId { get; init; } = ""; public int ImageCaptureFrameBufferCount { get; init; } public int ImageCaptureWidth { get; init; } public int ImageCaptureHeight { get; init; } public IConfigurationSection? CameraHardSettings { get; init; } public double DecodeGainR { get; set; } public double DecodeGainG { get; set; } public double DecodeGainB { get; set; } public int DecodeBlackLevel { get; set; } } 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 StreamConfiguration? _imageCaptureStreamConfiguration; private FrameBufferAllocator? _imageCaptureFrameBufferAllocator; private Stream? _imageCaptureStream; private List? _imageCaptureMappedBuffers; private System.Timers.ElapsedEventHandler? _viewFinderTimerElapsed = null; private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource(); private readonly Dictionary _cameraHardSettings = new Dictionary(); public bool IsImageCapturing { get; private set; } = false; public List ViewFinderBufferQueue { get; private set; } = new List(); public CameraServiceConfiguration Configuration => _configuration; public bool ViewFinderStarted { get; private set; } = false; public CameraService(ILogger logger, CameraServiceConfiguration? configuration = null, bool autoInitialize = false) { _logger = logger; _logger.LogInformation("Initialization"); try { if (configuration is null) { configuration = CameraServiceConfiguration.Default; _logger.LogWarning("Configuration not set. Using default configuration."); } _configuration = configuration; _logger.LogInformation("Using following configuration: \n{}", _configuration.ToString("\n", 4)); if (_configuration.CameraHardSettings is not null) { foreach (var child in _configuration.CameraHardSettings.GetChildren()) { object? value = child.Get(); if (value is string) { _cameraHardSettings.Add(child.Key, value); continue; } value = child.Get(); if (value is not null) { _cameraHardSettings.Add(child.Key, value); continue; } _logger.LogWarning("Cannot serialize camera hard settings value: {}: {}", child.Key, child.Value); } } if (autoInitialize) Initialize(CancellationToken.None); } catch (Exception e) { _logger.LogCritical(e, "An exception occurred while initialization"); throw; } _logger.LogInformation("Initialized"); } 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); ImageCaptureFrameBuffersAllocate(effectiveCancellationToken); CameraStart(effectiveCancellationToken); ImageCaptureFrameBuffersInitialize(effectiveCancellationToken); ImageCaptureFrameBuffersMap(effectiveCancellationToken); } catch (Exception e) { _logger.LogCritical(e, "An exception occurred while camera initialization"); Cleanup(); 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) { if (_cameraManager is null) throw new InvalidOperationException("CameraManager not initialized"); 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) { if (_camera is null) throw new InvalidOperationException("Camera not initialized"); 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) { if (_camera is null) throw new InvalidOperationException("Camera not initialized"); cancellationToken.ThrowIfCancellationRequested(); _cameraConfiguration = _camera.GenerateConfiguration(StreamRoleEnum.Raw); _imageCaptureStreamConfiguration = _cameraConfiguration.Skip(1).First(); _imageCaptureStreamConfiguration.BufferCount = checked((uint)_configuration.ImageCaptureFrameBufferCount); _imageCaptureStreamConfiguration.Size = new Size( checked((uint)_configuration.ImageCaptureWidth), checked((uint)_configuration.ImageCaptureHeight) ); if (_cameraConfiguration.Validate() == CameraConfiguration.Status.Invalid) { _logger.LogCritical("Error while camera configuration validation"); throw new Exception("Error while camera configuration validation"); } _logger.LogDebug(@$"ImageCapture stream configuration: Size: {_imageCaptureStreamConfiguration.Size.ToString(true)} FrameSize: {_imageCaptureStreamConfiguration.FrameSize} Stride: {_imageCaptureStreamConfiguration.Stride} ColorSpace: {_imageCaptureStreamConfiguration.ColorSpace?.ToString(true)} PixelFormat: {_imageCaptureStreamConfiguration.PixelFormat?.ToString(true)}"); 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}"); } _imageCaptureStream = _imageCaptureStreamConfiguration.Stream(); } private void CameraStart(CancellationToken cancellationToken) { if (_camera is null) throw new InvalidOperationException("Camera not initialized"); cancellationToken.ThrowIfCancellationRequested(); // int result = _camera.Start(); int result = _camera.Start(GetSettingsControlList(_cameraHardSettings)); if (result < 0) { _logger.LogCritical("Error while camera start {Result}", result); throw new Exception($"Error while camera start {result}"); } _logger.LogDebug("Camera settings: \n{}", GetSettings(4)); _state |= State.CameraStarted; } private void ImageCaptureFrameBuffersAllocate(CancellationToken cancellationToken) { if (_camera is null) throw new InvalidOperationException("Camera not initialized"); if (_imageCaptureStream is null) throw new InvalidOperationException("ImageCaptureStream not initialized"); cancellationToken.ThrowIfCancellationRequested(); _imageCaptureFrameBufferAllocator = new FrameBufferAllocator(_camera); cancellationToken.ThrowIfCancellationRequested(); int result = _imageCaptureFrameBufferAllocator.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 ImageCaptureFrameBuffersInitialize(CancellationToken cancellationToken) { if (_camera is null) throw new InvalidOperationException("Camera not initialized"); if (_imageCaptureStream is null) throw new InvalidOperationException("ImageCaptureStream not initialized"); if (_imageCaptureFrameBufferAllocator is null) throw new InvalidOperationException("ImageCaptureFrameBufferAllocator not initialized"); cancellationToken.ThrowIfCancellationRequested(); var buffers = _imageCaptureFrameBufferAllocator.Buffers(_imageCaptureStream); _logger.LogDebug("Image capture frame buffers initialization"); foreach (var buffer in buffers) { cancellationToken.ThrowIfCancellationRequested(); var request = _camera.CreateRequest(0); var result = request.AddBuffer(_imageCaptureStream, buffer); if (result < 0) throw new Exception("Error while request create"); var cts = new TaskCompletionSource(); void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e) { if (e.Request.Handle == request.Handle) cts.SetResult(); } _camera.RequestCompleted += RequestCompleted; result = _camera.QueueRequest(request); if (result < 0) { _camera.RequestCompleted -= RequestCompleted; throw new Exception("Error while request queue"); } cts.Task.WaitAsync(cancellationToken).GetAwaiter().GetResult(); _camera.RequestCompleted -= RequestCompleted; _logger.LogDebug("Request completed: \n\tResult: {} \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", result, request.Status, request.Cookie, request.Sequence, request.HasPendingBuffers); _logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp); foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate()) _logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed); foreach (var (i, plane) in buffer.Planes.Enumerate()) _logger.LogDebug("Plane[{}]: \n\tFd: 0x{:X} \n\tOffset: 0x{:X} \n\tLength: 0x{:X} \n\tKInvalidOffset: 0x{:X}", i, plane.Fd.Get(), plane.Offset, plane.Length, plane.KInvalidOffset); } } private void ImageCaptureFrameBuffersMap(CancellationToken cancellationToken) { if (_imageCaptureStream is null) throw new InvalidOperationException("ImageCaptureStream not initialized"); if (_imageCaptureFrameBufferAllocator is null) throw new InvalidOperationException("ImageCaptureFrameBufferAllocator not initialized"); cancellationToken.ThrowIfCancellationRequested(); _imageCaptureMappedBuffers = new List(); var frameBuffers = _imageCaptureFrameBufferAllocator.Buffers(_imageCaptureStream); foreach (var buffer in frameBuffers) { cancellationToken.ThrowIfCancellationRequested(); var planesByFd = buffer.Planes.GroupBy(p => p.Fd.Get()); foreach (var fdGroup in planesByFd) { cancellationToken.ThrowIfCancellationRequested(); int fd = fdGroup.Key; ulong minOffset = fdGroup.Min(p => p.Offset); ulong maxOffset = fdGroup.Max(p => p.Offset + p.Length); ulong pageSize = (ulong)Environment.SystemPageSize; ulong alignedOffset = minOffset & ~(pageSize - 1); ulong alignedLength = (maxOffset - alignedOffset + pageSize - 1) & ~(pageSize - 1); 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($"Error while buffers mapping, mmap failed for fd=0x{fd:X}, error={error}"); } _logger.LogDebug("Mapped buffer fd=0x{:X}, address=0x{:X}, length=0x{:X}", fd, address, alignedLength); _imageCaptureMappedBuffers.Add(new MappedBuffer { Fd = fd, Address = address, Length = alignedLength, AlignedOffset = alignedOffset }); } } _state |= State.ImageCaptureFrameBuffersMapped; } private string GetSettings(int indent = 0) { if (_camera is null) throw new InvalidOperationException("Camera not initialized"); var controls = _camera.Controls.ToList(); controls.Sort((c1, c2) => (int)(c1.Key.Id - c2.Key.Id)); var strs = new List>() { {["Id", "Name", "Type", "Direction", "IsArray", "Size", "Range", "Default", "Enumerators"]} }; foreach (var (id, info) in controls) { strs.Add([ id.Id.ToString(), id.Name.ToString(), id.Type.ToString(), id.Direction.ToString(), id.IsArray.ToString(), id.Size.ToString(), $"{info.Min.ToString(true)}...{info.Max.ToString(true)}", info.Def.ToString(true), string.Join(", ", id.Enumerators.FirstOrDefault()) ]); foreach (var enumerator in id.Enumerators.Skip(1)) strs.Add([ "", "", "", "", "", "", "", "", string.Join(", ", enumerator) ]); } var maxLengths = new List(); foreach (var str in strs) foreach (var (i, s) in str.Enumerate()) { if (i >= maxLengths.Count()) maxLengths.Add(0); maxLengths[i] = Math.Max(maxLengths[i], s.Length); } var sb = new StringBuilder(); foreach (var (i, str) in strs.Enumerate()) { foreach (var (j, s) in str.Enumerate()) sb.Append(new string(' ', indent) + string.Format($"{{0,-{maxLengths[j] + 1}}}", s)); if (i != strs.Count() - 1) sb.AppendLine(); } return sb.ToString(); } private void ApplyRequestSettings(Request request, Dictionary settings) { if (_camera is null) throw new InvalidOperationException("Camera not initialized"); foreach (var (id, info) in _camera.Controls) { if (!settings.TryGetValue(id.Name, out var parameter)) continue; ControlValue controlValue; try { controlValue = ControlValueHelper.Convert(parameter, id, info); } catch (Exception ex) { _logger.LogError(ex, "Error while converting value (control name:{}, control type:{}, value type:{}, value:{})", id.Name, id.Type, parameter.GetType(), parameter); continue; } request.Controls.Set(id.Id, controlValue); } } private ControlList GetSettingsControlList(Dictionary settings) { if (_camera is null) throw new InvalidOperationException("Camera not initialized"); var controlList = new ControlList(); foreach (var (id, info) in _camera.Controls) { if (!settings.TryGetValue(id.Name, out var parameter)) continue; ControlValue controlValue; try { controlValue = ControlValueHelper.Convert(parameter, id, info); } catch (Exception ex) { _logger.LogError(ex, "Error while converting value (control name:{}, control type:{}, value type:{}, value:{})", id.Name, id.Type, parameter.GetType(), parameter); continue; } controlList.Set(id.Id, controlValue); } return controlList; } public async Task CaptureImage(CancellationToken cancellationToken, int framesCount = 1, Dictionary? settings = null) { ArgumentOutOfRangeException.ThrowIfLessThan(framesCount, 1, nameof(framesCount)); if (_camera is null) throw new InvalidOperationException("Camera not initialized"); if (_imageCaptureStream is null) throw new InvalidOperationException("ImageCaptureStream not initialized"); if (_imageCaptureStreamConfiguration is null) throw new InvalidOperationException("ImageCaptureStreamConfiguration not initialized"); if (_imageCaptureFrameBufferAllocator is null) throw new InvalidOperationException("ImageCaptureFrameBufferAllocator not initialized"); if (_imageCaptureMappedBuffers is null) throw new InvalidOperationException("ImageCaptureMappedBuffers not mapped"); var effectiveCancellationToken = CancellationTokenSource .CreateLinkedTokenSource(_disposeCts.Token, cancellationToken) .Token; if (IsImageCapturing) return null; IsImageCapturing = true; _logger.LogDebug("Capturing {} frame(s)", framesCount); OpenCvSharp.Mat? image = null; var planesData = new List(); var fdsData = new Dictionary(); try { while (planesData.Count() < framesCount) { effectiveCancellationToken.ThrowIfCancellationRequested(); _logger.LogDebug("Requested {} frame(s), now collected: {}", framesCount, planesData.Count()); var oldPlanesDataCount = planesData.Count(); var buffers = _imageCaptureFrameBufferAllocator.Buffers(_imageCaptureStream); if (buffers.Count() > framesCount - planesData.Count()) buffers = buffers.Take(framesCount - planesData.Count()); _logger.LogDebug("Taken {} buffer(s)", buffers.Count()); var requests = new List(); foreach (var buffer in buffers) { var request = _camera.CreateRequest(1); var result = request.AddBuffer(_imageCaptureStream, buffer); if (result < 0) throw new Exception("Error while request create"); if (settings is not null) ApplyRequestSettings(request, settings); requests.Add(request); } _logger.LogDebug("{} request(s) created", requests.Count()); effectiveCancellationToken.ThrowIfCancellationRequested(); var cts = new TaskCompletionSource(); void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e) { if (!requests.Any(r => r.Handle == e.Request.Handle)) return; var buffer = e.Request.FindBuffer(_imageCaptureStream); if (buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameError || buffer.Metadata.Status == FrameMetadata.StatusEnum.FrameStartup) { _logger.LogDebug("Request buffers status: {}", buffer.Metadata.Status); e.Request.Reuse(Request.ReuseFlagEnum.ReuseBuffers); var result = _camera.QueueRequest(e.Request); if (result >= 0) return; } _logger.LogDebug("Request completed: \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", e.Request.Status, e.Request.Cookie, e.Request.Sequence, e.Request.HasPendingBuffers); _logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp); _logger.LogDebug("Request buffer planes: "); foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate()) _logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed); foreach (var (i, plane) in buffer.Planes.Enumerate()) _logger.LogDebug("Plane[{}]: \n\tFd: 0x{:X} \n\tOffset: 0x{:X} \n\tLength: 0x{:X} \n\tKInvalidOffset: 0x{:X}", i, plane.Fd.Get(), plane.Offset, plane.Length, plane.KInvalidOffset); effectiveCancellationToken.ThrowIfCancellationRequested(); foreach (var plane in buffer.Planes) { var fd = plane.Fd.Get(); if (fdsData.ContainsKey(fd)) continue; var mappedBuffer = _imageCaptureMappedBuffers.Find(b => b.Fd == fd); if (mappedBuffer is null) continue; var fdData = new byte[mappedBuffer.Length]; Marshal.Copy(mappedBuffer.Address, fdData, 0, checked((int)mappedBuffer.Length)); fdsData.Add(fd, fdData); } foreach (var plane in buffer.Planes) { var fd = plane.Fd.Get(); if (!fdsData.ContainsKey(fd)) continue; var fdData = fdsData[fd]; var planeData = fdData .Skip(checked((int)plane.Offset)) .Take(checked((int)plane.Length)) .ToArray(); planesData.Add(planeData); } _logger.LogDebug("Planes data added"); if (planesData.Count() - oldPlanesDataCount == requests.Count()) cts.SetResult(); } _camera.RequestCompleted += RequestCompleted; foreach (var request in requests) { var result = _camera.QueueRequest(request); if (result < 0) { _camera.RequestCompleted -= RequestCompleted; throw new Exception("Error while request queue"); } } _logger.LogDebug("{} request(s) queued", requests.Count()); await cts.Task.WaitAsync(effectiveCancellationToken); _logger.LogDebug("{} request(s) completed", requests.Count()); _camera.RequestCompleted -= RequestCompleted; foreach (var key in fdsData.Keys) #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. fdsData[key] = null; #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. fdsData.Clear(); } _logger.LogDebug("Frame(s) collected"); effectiveCancellationToken.ThrowIfCancellationRequested(); var config = _imageCaptureStreamConfiguration; 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=0x{fourcc:X}({new string(BitConverter.GetBytes(fourcc).Select(b => (char)b).ToArray())}) modifier=0x{modifier:X} primariesEnum={primariesEnum} transferFunctionEnum={transferFunctionEnum} ycbcrEncodingEnum={ycbcrEncodingEnum} rangeEnum={rangeEnum}"); _logger.LogDebug("Found decoder {}", decoder); effectiveCancellationToken.ThrowIfCancellationRequested(); try { image = decoder.Decode(checked((int)size.Width), checked((int)size.Height), checked((int)stride), planesData, _configuration.DecodeGainR, _configuration.DecodeGainG, _configuration.DecodeGainB, _configuration.DecodeBlackLevel); } catch (Exception ex) { _logger.LogError(ex, "Error while image decode"); throw; } _logger.LogDebug("Image decoded successfully"); } catch (Exception e) { _logger.LogError(e, "Error while image capture"); } finally { foreach (var key in fdsData.Keys) #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. fdsData[key] = null; #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. fdsData.Clear(); for (int i = 0; i < planesData.Count(); i++) #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. planesData[i] = null; #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. planesData.Clear(); } IsImageCapturing = false; return image; } 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 { if (_state.HasFlag(State.ImageCaptureFrameBuffersMapped) && _imageCaptureMappedBuffers is not null) { foreach (var buffer in _imageCaptureMappedBuffers) { _logger.LogDebug("MUNMAP address=0x{:X}, length=0x{:X}", buffer.Address, buffer.Length); LibC.munmap(buffer.Address, buffer.Length); } _imageCaptureMappedBuffers.Clear(); } if (_state.HasFlag(State.ImageCaptureFrameBufferAllocated) && _imageCaptureStream is not null) _imageCaptureFrameBufferAllocator?.Free(_imageCaptureStream); if (_state.HasFlag(State.CameraStarted)) _camera?.Stop(); if (_state.HasFlag(State.CameraAcquired)) _camera?.Release(); if (_state.HasFlag(State.CameraManagerStarted)) _cameraManager?.Stop(); _cameraManager?.Dispose(); } catch (Exception ex) { _logger.LogError(ex, "Error while CameraService cleanup"); } finally { _cameraManager = null; _camera = null; _imageCaptureFrameBufferAllocator = null; _imageCaptureStream = null; _imageCaptureMappedBuffers = null; _state = State.NotInitialized; } } }