diff --git a/src/MMALSharp.Processing/Handlers/CircularBufferCaptureHandler.cs b/src/MMALSharp.Processing/Handlers/CircularBufferCaptureHandler.cs
index 2e9ed910..43ffa50a 100644
--- a/src/MMALSharp.Processing/Handlers/CircularBufferCaptureHandler.cs
+++ b/src/MMALSharp.Processing/Handlers/CircularBufferCaptureHandler.cs
@@ -9,26 +9,17 @@
using Microsoft.Extensions.Logging;
using MMALSharp.Common;
using MMALSharp.Common.Utility;
-using MMALSharp.Processors;
-using MMALSharp.Processors.Motion;
namespace MMALSharp.Handlers
{
///
/// Represents a capture handler working as a circular buffer.
///
- public sealed class CircularBufferCaptureHandler : VideoStreamCaptureHandler, IMotionCaptureHandler
+ public sealed class CircularBufferCaptureHandler : VideoStreamCaptureHandler
{
private bool _recordToFileStream;
private int _bufferSize;
- private bool _shouldDetectMotion;
private bool _receivedIFrame;
- private int _recordNumFrames;
- private int _numFramesRecorded;
- private bool _splitFrames;
- private bool _beginRecordFrame;
- private IFrameAnalyser _analyser;
- private MotionConfig _motionConfig;
///
/// The circular buffer object responsible for storing image data.
@@ -81,132 +72,44 @@ public override void Process(ImageContext context)
this.Buffer.PushBack(context.Data[i]);
}
}
- else if (_recordNumFrames > 0)
- {
- // We will begin storing data immediately after we receive an EOS, this means we're sure to record frame data from the beginning of the stream.
- if (_beginRecordFrame)
- {
- this.CurrentStream.Write(context.Data, 0, context.Data.Length);
- this.Processed += context.Data.Length;
-
- if (context.Eos)
- {
- // We've reached the end of the frame. Check if we want to create a new file and increment number of recorded frames.
- _numFramesRecorded++;
-
- if (_numFramesRecorded >= _recordNumFrames)
- {
- // Effectively stop recording individual frames at this point.
- _beginRecordFrame = false;
- }
- }
- }
-
- if (context.Eos && _numFramesRecorded < _recordNumFrames)
- {
- _beginRecordFrame = true;
-
- if (_splitFrames)
- {
- this.Split();
- }
- }
- }
else
{
if (context.Encoding == MMALEncoding.H264)
{
- if (context.IFrame)
- {
- _receivedIFrame = true;
- }
+ _receivedIFrame = context.IFrame;
+ }
- if (_receivedIFrame && this.Buffer.Size > 0)
+ if (this.Buffer.Size > 0)
+ {
+ // The buffer contains data.
+ if (this.CurrentStream != null && this.CurrentStream.CanWrite)
{
- // The buffer contains data.
- MMALLog.Logger.LogInformation($"Buffer contains data. Writing {this.Buffer.Size} bytes.");
this.CurrentStream.Write(this.Buffer.ToArray(), 0, this.Buffer.Size);
- this.Processed += this.Buffer.Size;
- this.Buffer = new CircularBuffer(this.Buffer.Capacity);
}
- if (_receivedIFrame)
- {
- // We need to have received an IFrame for the recording to be valid.
- this.CurrentStream.Write(context.Data, 0, context.Data.Length);
- this.Processed += context.Data.Length;
- }
+ this.Processed += this.Buffer.Size;
+ this.Buffer = new CircularBuffer(this.Buffer.Capacity);
}
- else
- {
- if (this.Buffer.Size > 0)
- {
- // The buffer contains data.
- this.CurrentStream.Write(this.Buffer.ToArray(), 0, this.Buffer.Size);
- this.Processed += this.Buffer.Size;
- this.Buffer = new CircularBuffer(this.Buffer.Capacity);
- }
+ if (this.CurrentStream != null && this.CurrentStream.CanWrite)
+ {
this.CurrentStream.Write(context.Data, 0, context.Data.Length);
- this.Processed += context.Data.Length;
}
- }
- if (_shouldDetectMotion && !_recordToFileStream)
- {
- _analyser?.Apply(context);
+ this.Processed += context.Data.Length;
}
// Not calling base method to stop data being written to the stream when not recording.
this.ImageContext = context;
}
- ///
- public void ConfigureMotionDetection(MotionConfig config, Action onDetect)
- {
- _motionConfig = config;
-
- switch(this.MotionType)
- {
- case MotionType.FrameDiff:
- _analyser = new FrameDiffAnalyser(config, onDetect);
- break;
-
- case MotionType.MotionVector:
- // TODO Motion vector analyser
- break;
- }
-
- this.EnableMotionDetection();
- }
-
- ///
- public void EnableMotionDetection()
- {
- _shouldDetectMotion = true;
-
- MMALLog.Logger.LogInformation("Enabling motion detection.");
- }
-
- ///
- public void DisableMotionDetection()
- {
- _shouldDetectMotion = false;
-
- (_analyser as FrameDiffAnalyser)?.ResetAnalyser();
-
- MMALLog.Logger.LogInformation("Disabling motion detection.");
- }
-
///
/// Call to start recording to FileStream.
///
/// Optional Action to execute when recording starts, for example, to request an h.264 I-frame.
/// When the token is canceled, is called. If a token is not provided, the caller must stop the recording.
- /// Optional number of full frames to record. If value is 0, parameter will be used to manage timeout.
- /// Optional flag to state full frames should be split to new files.
/// Task representing the recording process if a CancellationToken was provided, otherwise a completed Task.
- public async Task StartRecording(Action initRecording = null, CancellationToken cancellationToken = default, int recordNumFrames = 0, bool splitFrames = false)
+ public async Task StartRecording(Action initRecording = null, CancellationToken cancellationToken = default)
{
if (this.CurrentStream == null)
{
@@ -214,9 +117,7 @@ public async Task StartRecording(Action initRecording = null, CancellationToken
}
_recordToFileStream = true;
- _recordNumFrames = recordNumFrames;
- _splitFrames = splitFrames;
-
+
if (initRecording != null)
{
initRecording.Invoke();
@@ -251,12 +152,6 @@ public void StopRecording()
_recordToFileStream = false;
_receivedIFrame = false;
- _beginRecordFrame = false;
- _recordNumFrames = 0;
- _numFramesRecorded = 0;
- _splitFrames = false;
-
- (_analyser as FrameDiffAnalyser)?.ResetAnalyser();
}
///
diff --git a/src/MMALSharp.Processing/Handlers/FrameBufferCaptureHandler.cs b/src/MMALSharp.Processing/Handlers/FrameBufferCaptureHandler.cs
new file mode 100644
index 00000000..7a3ab7a5
--- /dev/null
+++ b/src/MMALSharp.Processing/Handlers/FrameBufferCaptureHandler.cs
@@ -0,0 +1,162 @@
+//
+// Copyright (c) Ian Auty and contributors. All rights reserved.
+// Licensed under the MIT License. Please see LICENSE.txt for License info.
+//
+
+using System;
+using System.IO;
+using MMALSharp.Common;
+using MMALSharp.Processors.Motion;
+
+namespace MMALSharp.Handlers
+{
+ ///
+ /// A capture handler focused on high-speed frame buffering, either for on-demand snapshots
+ /// or for motion detection.
+ ///
+ public class FrameBufferCaptureHandler : MemoryStreamCaptureHandler, IMotionCaptureHandler, IVideoCaptureHandler
+ {
+ private MotionConfig _motionConfig;
+ private bool _detectingMotion;
+ private FrameDiffAnalyser _motionAnalyser;
+
+ private bool _waitForFullFrame = true;
+ private bool _writeFrameRequested = false;
+
+ ///
+ /// Creates a new optionally configured to write on-demand snapshots.
+ ///
+ /// Target path for image files
+ /// Extension for image files
+ /// Filename DateTime formatting string
+ public FrameBufferCaptureHandler(string directory = "", string extension = "", string fileDateTimeFormat = "yyyy-MM-dd HH.mm.ss.ffff")
+ : base()
+ {
+ this.FileDirectory = directory.TrimEnd('/');
+ this.FileExtension = extension;
+ this.FileDateTimeFormat = fileDateTimeFormat;
+ Directory.CreateDirectory(this.FileDirectory);
+ }
+
+ ///
+ /// Creates a new configured for motion detection using a raw video stream.
+ ///
+ public FrameBufferCaptureHandler()
+ : base()
+ { }
+
+ ///
+ /// Target directory when is invoked without a directory argument.
+ ///
+ public string FileDirectory { get; set; } = string.Empty;
+
+ ///
+ /// File extension when is invoked without an extension argument.
+ ///
+ public string FileExtension { get; set; } = string.Empty;
+
+ ///
+ /// Filename format when is invoked without a format argument.
+ ///
+ public string FileDateTimeFormat { get; set; } = string.Empty;
+
+ ///
+ /// The filename (without extension) most recently created by , if any.
+ ///
+ public string MostRecentFilename { get; set; } = string.Empty;
+
+ ///
+ /// The full pathname to the most recent file created by , if any.
+ ///
+ public string MostRecentPathname { get; set; } = string.Empty;
+
+ ///
+ public MotionType MotionType { get; set; } = MotionType.FrameDiff;
+
+ ///
+ /// Outputs an image file to the specified location and filename.
+ ///
+ public void WriteFrame()
+ {
+ if (string.IsNullOrWhiteSpace(this.FileDirectory) || string.IsNullOrWhiteSpace(this.FileDateTimeFormat))
+ throw new Exception($"The {nameof(FileDirectory)} and {nameof(FileDateTimeFormat)} must be set before calling {nameof(WriteFrame)}");
+
+ _writeFrameRequested = true;
+ }
+
+ ///
+ public override void Process(ImageContext context)
+ {
+ // guard against partial frame data at startup
+ if (_waitForFullFrame)
+ {
+ _waitForFullFrame = !context.Eos;
+ if (_waitForFullFrame)
+ {
+ return;
+ }
+ }
+
+ if (_detectingMotion)
+ {
+ _motionAnalyser.Apply(context);
+ }
+
+ // accumulate frame data in the underlying memory stream
+ base.Process(context);
+
+ if (context.Eos)
+ {
+ // write a full frame if a request is pending
+ if (_writeFrameRequested)
+ {
+ this.WriteStreamToFile();
+ _writeFrameRequested = false;
+ }
+
+ // reset the stream to begin the next frame
+ this.CurrentStream.SetLength(0);
+ }
+ }
+
+ ///
+ public void ConfigureMotionDetection(MotionConfig config, Action onDetect)
+ {
+ _motionConfig = config;
+ _motionAnalyser = new FrameDiffAnalyser(config, onDetect);
+ this.EnableMotionDetection();
+ }
+
+ ///
+ public void EnableMotionDetection()
+ {
+ _detectingMotion = true;
+ _motionAnalyser?.ResetAnalyser();
+ }
+
+ ///
+ public void DisableMotionDetection()
+ {
+ _detectingMotion = false;
+ }
+
+ ///
+ public void Split()
+ { } // Unused, but required to handle a video stream.
+
+ private void WriteStreamToFile()
+ {
+ string directory = this.FileDirectory.TrimEnd('/');
+ string filename = DateTime.Now.ToString(this.FileDateTimeFormat);
+ string pathname = $"{directory}/{filename}.{this.FileExtension}";
+
+ using (var fs = new FileStream(pathname, FileMode.Create, FileAccess.Write))
+ {
+ CurrentStream.WriteTo(fs);
+ }
+
+ this.MostRecentFilename = filename;
+ this.MostRecentPathname = pathname;
+ }
+ }
+}