📄 src/Integrations/FaceCamera/FaceDetectionService.cs
using System;
using System.Threading;
using Emgu.CV;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace MMirror.Integrations.FaceCamera;

public sealed class FaceDetectionService : IDisposable
{
    private readonly ILogger<FaceDetectionService> logger;
    private readonly VideoCapture videoCapture;
    private readonly FaceDetectorYN faceDetector;

    private readonly Lock watchLock = new();
    private Action<ReadOnlySpan<FaceDetection>>? faceDetected;
    public event Action<ReadOnlySpan<FaceDetection>> FaceDetected
    {
        add
        {
            using var scope = watchLock.EnterScope();
            if (faceDetected is null)
            {
                videoCapture.Start();
            }
            faceDetected += value;
        }
        remove
        {
            using var scope = watchLock.EnterScope();
            faceDetected -= value;
            if (faceDetected is null)
            {
                videoCapture.Stop();
            }
        }
    }

    public FaceDetectionService(ILogger<FaceDetectionService> logger, IOptions<CameraOptions> options)
    {
        this.logger = logger;

        videoCapture = new(options.Value.Index);
        videoCapture.ImageGrabbed += OnImageGrabbed;

        faceDetector = new FaceDetectorYN(
            model: options.Value.Model,
            config: options.Value.Config,
            inputSize: new(videoCapture.Width, videoCapture.Height)
        );
    }

    private void OnImageGrabbed(object? sender, EventArgs e)
    {
        using var frame = new Mat();
        if (!videoCapture.Retrieve(frame))
        {
            logger.LogError("Could not retrieve frame on frame event.");
            return;
        }

        using var detections = new Mat();
        if (faceDetector.Detect(frame, detections) is not 1)
        {
            logger.LogError("Facial detection failed.");
            return;
        }

        var faces = detections.GetSpan<FaceDetection>();
        faceDetected?.Invoke(faces);
    }

    public void Dispose()
    {
        videoCapture.Dispose();
        faceDetector.Dispose();
    }
}