1

I am trying to record video from a webcam using OpenCvSharp

I can already record the video using the code below but the resulting .mp4 file plays way to fast (e.g. i record for 5 seconds and the result isn't even one second long).

I already played with the delay in AddCameraFrameToRecordingThread but to no avail

What can possibly be the problem? Or what other library can I use to record a video from webcam?

namespace BlackBears.Recording
{
    using System;
    using System.Drawing;
    using System.Threading;

    using OpenCvSharp;
    using OpenCvSharp.Extensions;

    using Size = OpenCvSharp.Size;

    public class Recorder : IDisposable
    {
        private readonly VideoCaptureAPIs _videoCaptureApi = VideoCaptureAPIs.DSHOW;
        private readonly ManualResetEventSlim _writerReset = new(false);
        private readonly VideoCapture _videoCapture;
        private VideoWriter _videoWriter;
        private Thread _writerThread;

        private bool IsVideoCaptureValid => _videoCapture is not null && _videoCapture.IsOpened();

        public Recorder(int deviceIndex, int frameWidth, int frameHeight, double fps)
        {
            _videoCapture = VideoCapture.FromCamera(deviceIndex, _videoCaptureApi);
            _videoCapture.Open(deviceIndex, _videoCaptureApi);

            _videoCapture.FrameWidth = frameWidth;
            _videoCapture.FrameHeight = frameHeight;
            _videoCapture.Fps = fps;
        }

        /// <inheritdoc />
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Dispose(true);
        }

        ~Recorder()
        {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                StopRecording();

                _videoCapture?.Release();
                _videoCapture?.Dispose();
            }
        }

        public void StartRecording(string path)
        {
            if (_writerThread is not null)
                return;

            if (!IsVideoCaptureValid)
                ThrowHelper.ThrowVideoCaptureNotReadyException();

            _videoWriter = new VideoWriter(path, FourCC.XVID, _videoCapture.Fps, new Size(_videoCapture.FrameWidth, _videoCapture.FrameHeight));

            _writerReset.Reset();
            _writerThread = new Thread(AddCameraFrameToRecordingThread);
            _writerThread.Start();
        }

        public void StopRecording()
        {
            if (_writerThread is not null)
            {
                _writerReset.Set();
                _writerThread.Join();
                _writerThread = null;
                _writerReset.Reset();
            }

            _videoWriter?.Release();
            _videoWriter?.Dispose();
            _videoWriter = null;
        }

        private void AddCameraFrameToRecordingThread()
        {
            var waitTimeBetweenFrames = (int)(1_000 / _videoCapture.Fps);
            using var frame = new Mat();
            while (!_writerReset.Wait(waitTimeBetweenFrames))
            {
                if (!_videoCapture.Read(frame))
                    return;
                _videoWriter.Write(frame);
            }
        }
    }
}
Gargo
  • 621
  • 6
  • 17

1 Answers1

0

I found a solution myself after a lot more playing around.

A single thread to capture the frames and write them was not enough. I now created two threads, one that captures the frames from the camera and one that writes them. To have the correct timing in the resulting file the delay the write creates has to be taken into account.

I ended up with the following two functions that run in separate threads:

private void CaptureFrameLoop()
{
    while (!_threadStopEvent.Wait(0))
    {
        _videoCapture.Read(_capturedFrame);
    }
}

private void AddCameraFrameToRecordingThread()
{
    var waitTimeBetweenFrames = 1_000 / _videoCapture.Fps;
    var lastWrite = DateTime.Now;

    while (!_threadStopEvent.Wait(0))
    {
        if (DateTime.Now.Subtract(lastWrite).TotalMilliseconds < waitTimeBetweenFrames)
            continue;
        lastWrite = DateTime.Now;
        _videoWriter.Write(_capturedFrame);
    }
}
Gargo
  • 621
  • 6
  • 17