From 63061a03b4adaac357f13ad21fac7ee222e89acb Mon Sep 17 00:00:00 2001 From: Jon McGuire Date: Tue, 4 Aug 2020 14:38:44 -0400 Subject: [PATCH] see MMALSharp issue #163 --- .../Handlers/OnDemandImageCaptureHandler.cs | 184 ++++++++++++++++++ src/MMALSharp.Processing/IStreamWriter.cs | 28 +++ .../FastImageOutputCallbackHandler.cs | 20 +- 3 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 src/MMALSharp.Processing/Handlers/OnDemandImageCaptureHandler.cs create mode 100644 src/MMALSharp.Processing/IStreamWriter.cs diff --git a/src/MMALSharp.Processing/Handlers/OnDemandImageCaptureHandler.cs b/src/MMALSharp.Processing/Handlers/OnDemandImageCaptureHandler.cs new file mode 100644 index 00000000..9372932b --- /dev/null +++ b/src/MMALSharp.Processing/Handlers/OnDemandImageCaptureHandler.cs @@ -0,0 +1,184 @@ +// +// Copyright (c) Ian Auty and contributors. All rights reserved. +// Licensed under the MIT License. Please see LICENSE.txt for License info. +// + +using Microsoft.Extensions.Logging; +using MMALSharp.Common.Utility; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace MMALSharp.Handlers +{ + /// + /// Buffers single-frame Image data to a until is called. + /// + public class OnDemandImageCaptureHandler : MemoryStreamCaptureHandler, IStreamWriter, IFileStreamCaptureHandler + { + private readonly string _customFilename; + private int _increment; + + /// + /// A list of files that have been processed by this capture handler. + /// + public List ProcessedFiles { get; set; } = new List(); + + /// + /// The directory to save to (if applicable). + /// + public string Directory { get; set; } + + /// + /// The extension of the file (if applicable). + /// + public string Extension { get; set; } + + /// + /// The full pathname of the most-recently-written file. + /// + public string MostRecentPathname { get; set; } + + /// + /// When true, the underlying callback handler will write the to when the frame is completed. + /// + public bool WriteRequested { get; set; } + + /// + /// Creates a new instance of the class with the specified directory and filename extension. + /// + /// The directory to save captured images. + /// The filename extension for saving files. + public OnDemandImageCaptureHandler(string directory, string extension) + : base() + { + this.Directory = directory.TrimEnd('/'); + this.Extension = extension.TrimStart('.'); + + MMALLog.Logger.LogDebug($"{nameof(OnDemandImageCaptureHandler)} created for directory {this.Directory} and extension {this.Extension}"); + + System.IO.Directory.CreateDirectory(this.Directory); + + this.MostRecentPathname = string.Empty; + this.CurrentStream = new MemoryStream(); + } + + /// + /// Creates a new instance of the class with the specified file path. + /// + /// The absolute full path to save captured data to. + public OnDemandImageCaptureHandler(string fullPath) + : base() + { + + var ext = fullPath.Split('.').LastOrDefault(); + if (string.IsNullOrEmpty(ext)) + { + throw new ArgumentNullException(nameof(ext), "Could not get file extension from path string."); + } + + this.Extension = ext; + var fileInfo = new FileInfo(fullPath); + this.Directory = fileInfo.DirectoryName; + _customFilename = Path.GetFileNameWithoutExtension(fileInfo.Name); + + MMALLog.Logger.LogDebug($"{nameof(OnDemandImageCaptureHandler)} created for directory {this.Directory} and extension {this.Extension}"); + + System.IO.Directory.CreateDirectory(this.Directory); + + this.MostRecentPathname = string.Empty; + this.CurrentStream = new MemoryStream(); + } + + /// + /// This handler doesn't create a file in advance. This will return an empty string. + /// + /// Empty string. + public string GetFilename() => string.Empty; + + /// + /// This handler doesn't create a file in advance. This will return an empty string. + /// + /// Empty string. + public string GetFilepath() => string.Empty; + + /// + /// Signals the underlying callback handler to call when the frame is completely captured. + /// + public virtual void NewFile() + { + if (this.CurrentStream == null) + { + return; + } + + WriteRequested = true; + } + + /// + /// The callback handler uses this to write the current completed buffer to a file. + /// + public void WriteStreamToFile() + { + if (this.CurrentStream == null || !this.WriteRequested) + { + return; + } + + string newFilename; + if (!string.IsNullOrEmpty(_customFilename)) + { + // If we're taking photos from video port, we don't want to be hammering File.Exists as this is added I/O overhead. Camera can take multiple photos per second + // so we can't do this when filename uses the current DateTime. + _increment++; + newFilename = $"{this.Directory}/{_customFilename} {_increment}.{this.Extension}"; + } + else + { + string tempFilename = DateTime.Now.ToString("dd-MMM-yy HH-mm-ss"); + int i = 1; + + newFilename = $"{this.Directory}/{tempFilename}.{this.Extension}"; + + while (File.Exists(newFilename)) + { + newFilename = $"{this.Directory}/{tempFilename} {i}.{this.Extension}"; + i++; + } + } + + using (FileStream fs = new FileStream(newFilename, FileMode.Create, FileAccess.Write)) + { + this.CurrentStream.WriteTo(fs); + } + + this.MostRecentPathname = newFilename; + this.WriteRequested = false; + } + + /// + /// Resets the underlying without re-allocating. + /// + public void ResetStream() + => this.CurrentStream.SetLength(0); + + /// + public override void PostProcess() + { + if (this.CurrentStream == null) + { + return; + } + + this.ProcessedFiles.Add(new ProcessedFileResult(this.Directory, Path.GetFileNameWithoutExtension(this.MostRecentPathname), this.Extension)); + base.PostProcess(); + } + + /// + public override string TotalProcessed() + { + return $"{this.Processed}"; + } + } +} diff --git a/src/MMALSharp.Processing/IStreamWriter.cs b/src/MMALSharp.Processing/IStreamWriter.cs new file mode 100644 index 00000000..54460954 --- /dev/null +++ b/src/MMALSharp.Processing/IStreamWriter.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Ian Auty and contributors. All rights reserved. +// Licensed under the MIT License. Please see LICENSE.txt for License info. +// + +namespace MMALSharp.Handlers +{ + /// + /// Provides the functionality to write a stream to storage. + /// + public interface IStreamWriter + { + /// + /// When true, the object is preparing to write the stream (it may require additional work such as waiting for the end of a frame). + /// + bool WriteRequested { get; set; } + + /// + /// Immediately output the stream to a file. Not a request, typically in response to end-of-frame when is true. + /// + void WriteStreamToFile(); + + /// + /// Discard the current data buffered in the stream. + /// + void ResetStream(); + } +} diff --git a/src/MMALSharp/Callbacks/FastImageOutputCallbackHandler.cs b/src/MMALSharp/Callbacks/FastImageOutputCallbackHandler.cs index 487b6ea4..9056514a 100644 --- a/src/MMALSharp/Callbacks/FastImageOutputCallbackHandler.cs +++ b/src/MMALSharp/Callbacks/FastImageOutputCallbackHandler.cs @@ -32,9 +32,25 @@ public override void Callback(IBuffer buffer) var eos = buffer.AssertProperty(MMALBufferProperties.MMAL_BUFFER_HEADER_FLAG_FRAME_END) || buffer.AssertProperty(MMALBufferProperties.MMAL_BUFFER_HEADER_FLAG_EOS); - if (eos && this.CaptureHandler is IFileStreamCaptureHandler) + if(eos) { - ((IFileStreamCaptureHandler)this.CaptureHandler).NewFile(); + // try this first since it probably also implements IFileStreamCaptureHandler + var writer = this.CaptureHandler as IStreamWriter; + if(writer != null) + { + if(writer.WriteRequested) + { + writer.WriteStreamToFile(); + } + + writer.ResetStream(); + } + else + { + // continuously writes every frame + var fsHandler = this.CaptureHandler as IFileStreamCaptureHandler; + fsHandler?.NewFile(); + } } } }