Skip to content
This repository has been archived by the owner on Mar 28, 2024. It is now read-only.

Commit

Permalink
New FrameBufferCaptureHandler and revised CircularBufferCaptureHandler (
Browse files Browse the repository at this point in the history
techyian#169)

* New FrameBufferCaptureHandler and revised CircularBufferCaptureHandler

* added null and CanWrite checks
  • Loading branch information
MV10 authored Aug 30, 2020
1 parent ed27443 commit 49f14f8
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 119 deletions.
133 changes: 14 additions & 119 deletions src/MMALSharp.Processing/Handlers/CircularBufferCaptureHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// Represents a capture handler working as a circular buffer.
/// </summary>
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;

/// <summary>
/// The circular buffer object responsible for storing image data.
Expand Down Expand Up @@ -81,142 +72,52 @@ 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<byte>(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<byte>(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<byte>(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;
}

/// <inheritdoc/>
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();
}

/// <inheritdoc/>
public void EnableMotionDetection()
{
_shouldDetectMotion = true;

MMALLog.Logger.LogInformation("Enabling motion detection.");
}

/// <inheritdoc/>
public void DisableMotionDetection()
{
_shouldDetectMotion = false;

(_analyser as FrameDiffAnalyser)?.ResetAnalyser();

MMALLog.Logger.LogInformation("Disabling motion detection.");
}

/// <summary>
/// Call to start recording to FileStream.
/// </summary>
/// <param name="initRecording">Optional Action to execute when recording starts, for example, to request an h.264 I-frame.</param>
/// <param name="cancellationToken">When the token is canceled, <see cref="StopRecording"/> is called. If a token is not provided, the caller must stop the recording.</param>
/// <param name="recordNumFrames">Optional number of full frames to record. If value is 0, <paramref name="cancellationToken"/> parameter will be used to manage timeout.</param>
/// <param name="splitFrames">Optional flag to state full frames should be split to new files.</param>
/// <returns>Task representing the recording process if a CancellationToken was provided, otherwise a completed Task.</returns>
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)
{
throw new InvalidOperationException($"Recording unavailable, {nameof(CircularBufferCaptureHandler)} was not created with output-file arguments");
}

_recordToFileStream = true;
_recordNumFrames = recordNumFrames;
_splitFrames = splitFrames;


if (initRecording != null)
{
initRecording.Invoke();
Expand Down Expand Up @@ -251,12 +152,6 @@ public void StopRecording()

_recordToFileStream = false;
_receivedIFrame = false;
_beginRecordFrame = false;
_recordNumFrames = 0;
_numFramesRecorded = 0;
_splitFrames = false;

(_analyser as FrameDiffAnalyser)?.ResetAnalyser();
}

/// <inheritdoc />
Expand Down
162 changes: 162 additions & 0 deletions src/MMALSharp.Processing/Handlers/FrameBufferCaptureHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// <copyright file="FrameBufferCaptureHandler.cs" company="Techyian">
// Copyright (c) Ian Auty and contributors. All rights reserved.
// Licensed under the MIT License. Please see LICENSE.txt for License info.
// </copyright>

using System;
using System.IO;
using MMALSharp.Common;
using MMALSharp.Processors.Motion;

namespace MMALSharp.Handlers
{
/// <summary>
/// A capture handler focused on high-speed frame buffering, either for on-demand snapshots
/// or for motion detection.
/// </summary>
public class FrameBufferCaptureHandler : MemoryStreamCaptureHandler, IMotionCaptureHandler, IVideoCaptureHandler
{
private MotionConfig _motionConfig;
private bool _detectingMotion;
private FrameDiffAnalyser _motionAnalyser;

private bool _waitForFullFrame = true;
private bool _writeFrameRequested = false;

/// <summary>
/// Creates a new <see cref="FrameBufferCaptureHandler"/> optionally configured to write on-demand snapshots.
/// </summary>
/// <param name="directory">Target path for image files</param>
/// <param name="extension">Extension for image files</param>
/// <param name="fileDateTimeFormat">Filename DateTime formatting string</param>
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);
}

/// <summary>
/// Creates a new <see cref="FrameBufferCaptureHandler"/> configured for motion detection using a raw video stream.
/// </summary>
public FrameBufferCaptureHandler()
: base()
{ }

/// <summary>
/// Target directory when <see cref="WriteFrame"/> is invoked without a directory argument.
/// </summary>
public string FileDirectory { get; set; } = string.Empty;

/// <summary>
/// File extension when <see cref="WriteFrame"/> is invoked without an extension argument.
/// </summary>
public string FileExtension { get; set; } = string.Empty;

/// <summary>
/// Filename format when <see cref="WriteFrame"/> is invoked without a format argument.
/// </summary>
public string FileDateTimeFormat { get; set; } = string.Empty;

/// <summary>
/// The filename (without extension) most recently created by <see cref="WriteFrame"/>, if any.
/// </summary>
public string MostRecentFilename { get; set; } = string.Empty;

/// <summary>
/// The full pathname to the most recent file created by <see cref="WriteFrame"/>, if any.
/// </summary>
public string MostRecentPathname { get; set; } = string.Empty;

/// <inheritdoc />
public MotionType MotionType { get; set; } = MotionType.FrameDiff;

/// <summary>
/// Outputs an image file to the specified location and filename.
/// </summary>
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;
}

/// <inheritdoc />
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);
}
}

/// <inheritdoc />
public void ConfigureMotionDetection(MotionConfig config, Action onDetect)
{
_motionConfig = config;
_motionAnalyser = new FrameDiffAnalyser(config, onDetect);
this.EnableMotionDetection();
}

/// <inheritdoc />
public void EnableMotionDetection()
{
_detectingMotion = true;
_motionAnalyser?.ResetAnalyser();
}

/// <inheritdoc />
public void DisableMotionDetection()
{
_detectingMotion = false;
}

/// <inheritdoc />
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;
}
}
}

0 comments on commit 49f14f8

Please sign in to comment.