From e5cf493f7ec4e0b07db239750c4a1f976b94b466 Mon Sep 17 00:00:00 2001 From: techyian Date: Fri, 7 Apr 2017 18:23:44 +0100 Subject: [PATCH] Moved ICaptureHandler to MMALDownstreamComponent constructor to allow for more refined use of 'using' statements and clearup of unmanaged resources. Moved FFmpeg specific code to another project. Updated build scripts and README --- MMALSharp.Common/Handlers/ICaptureHandler.cs | 21 ++ .../Handlers/ImageStreamCaptureHandler.cs | 13 + .../Handlers/ProcessResult.cs | 0 .../Handlers/StreamCaptureHandler.cs | 86 +++++ .../Handlers/VideoStreamCaptureHandler.cs | 29 ++ MMALSharp.Common/MMALSharp.Common.csproj | 59 ++++ MMALSharp.Common/Properties/AssemblyInfo.cs | 36 ++ .../Utility/Helpers.cs | 0 .../Handlers/FFmpegCaptureHandler.cs | 8 +- MMALSharp.FFmpeg/MMALSharp.FFmpeg.csproj | 61 ++++ MMALSharp.FFmpeg/Properties/AssemblyInfo.cs | 36 ++ MMALSharp.FFmpeg/VideoUtilities.cs | 40 +++ MMALSharp.sln | 22 ++ .../Components/MMALDownstreamComponent.cs | 34 +- MMALSharp/Components/MMALEncoderComponent.cs | 36 +- MMALSharp/Components/MMALRendererComponent.cs | 2 +- MMALSharp/Components/MMALSplitterComponent.cs | 2 +- MMALSharp/Handlers/ICaptureHandler.cs | 35 -- MMALSharp/Handlers/StreamCaptureResult.cs | 94 ------ MMALSharp/MMALCamera.cs | 309 +++++------------- MMALSharp/MMALSharp.csproj | 21 +- MMALSharp/Native/MMALUtil.cs | 2 +- MMALSharpExample/MMALSharpExample.csproj | 4 + MMALSharpExample/Program.cs | 55 ++-- README.md | 51 +-- build.cmd | 4 +- build.fsx | 51 ++- build.sh | 6 +- 28 files changed, 665 insertions(+), 452 deletions(-) create mode 100644 MMALSharp.Common/Handlers/ICaptureHandler.cs create mode 100644 MMALSharp.Common/Handlers/ImageStreamCaptureHandler.cs rename {MMALSharp => MMALSharp.Common}/Handlers/ProcessResult.cs (100%) create mode 100644 MMALSharp.Common/Handlers/StreamCaptureHandler.cs create mode 100644 MMALSharp.Common/Handlers/VideoStreamCaptureHandler.cs create mode 100644 MMALSharp.Common/MMALSharp.Common.csproj create mode 100644 MMALSharp.Common/Properties/AssemblyInfo.cs rename {MMALSharp => MMALSharp.Common}/Utility/Helpers.cs (100%) rename {MMALSharp => MMALSharp.FFmpeg}/Handlers/FFmpegCaptureHandler.cs (92%) create mode 100644 MMALSharp.FFmpeg/MMALSharp.FFmpeg.csproj create mode 100644 MMALSharp.FFmpeg/Properties/AssemblyInfo.cs create mode 100644 MMALSharp.FFmpeg/VideoUtilities.cs delete mode 100644 MMALSharp/Handlers/ICaptureHandler.cs delete mode 100644 MMALSharp/Handlers/StreamCaptureResult.cs diff --git a/MMALSharp.Common/Handlers/ICaptureHandler.cs b/MMALSharp.Common/Handlers/ICaptureHandler.cs new file mode 100644 index 00000000..609f487d --- /dev/null +++ b/MMALSharp.Common/Handlers/ICaptureHandler.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MMALSharp.Handlers +{ + public interface ICaptureHandler : IDisposable + { + /// + /// Used to process the byte array containing our image data + /// + /// A byte array containing image data + void Process(byte[] data); + /// + /// Used for any further processing once we have completed capture + /// + void PostProcess(); + } +} diff --git a/MMALSharp.Common/Handlers/ImageStreamCaptureHandler.cs b/MMALSharp.Common/Handlers/ImageStreamCaptureHandler.cs new file mode 100644 index 00000000..b1fc9518 --- /dev/null +++ b/MMALSharp.Common/Handlers/ImageStreamCaptureHandler.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MMALSharp.Handlers +{ + public class ImageStreamCaptureHandler : StreamCaptureHandler + { + public ImageStreamCaptureHandler(string directory, string extension) : base(directory, extension) { } + } +} diff --git a/MMALSharp/Handlers/ProcessResult.cs b/MMALSharp.Common/Handlers/ProcessResult.cs similarity index 100% rename from MMALSharp/Handlers/ProcessResult.cs rename to MMALSharp.Common/Handlers/ProcessResult.cs diff --git a/MMALSharp.Common/Handlers/StreamCaptureHandler.cs b/MMALSharp.Common/Handlers/StreamCaptureHandler.cs new file mode 100644 index 00000000..a6211224 --- /dev/null +++ b/MMALSharp.Common/Handlers/StreamCaptureHandler.cs @@ -0,0 +1,86 @@ +using MMALSharp.Utility; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MMALSharp.Handlers +{ + /// + /// Processes the image data to a stream. + /// + public abstract class StreamCaptureHandler : ICaptureHandler + { + protected Stream CurrentStream { get; set; } + public List ProcessedStreams { get; set; } = new List(); + protected int Processed { get; set; } + protected string Directory { get; set; } + protected string Extension { get; set; } + + public StreamCaptureHandler(string directory, string extension) + { + this.Directory = directory.TrimEnd('/'); + this.Extension = extension.TrimStart('.'); + } + + public void NewFile() + { + if (this.CurrentStream != null) + this.CurrentStream.Dispose(); + this.CurrentStream = File.Create(this.Directory + "/" + DateTime.Now.ToString("dd-MMM-yy HH-mm-ss") + "." + this.Extension); + } + + public void Process(byte[] data) + { + this.Processed += data.Length; + + if (this.CurrentStream.CanWrite) + this.CurrentStream.Write(data, 0, data.Length); + else + throw new IOException("Stream not writable."); + + } + + public void PostProcess() + { + this.ProcessedStreams.Add(this.GetDirectory() + "/" + this.GetFilename() + "." + this.GetExtension()); + Console.WriteLine(string.Format("Successfully processed {0}", Helpers.ConvertBytesToMegabytes(this.Processed))); + } + + public string GetDirectory() + { + if(this.CurrentStream.GetType() == typeof(FileStream)) + { + return Path.GetDirectoryName(((FileStream)this.CurrentStream).Name); + } + return null; + } + + private string GetExtension() + { + if (this.CurrentStream.GetType() == typeof(FileStream)) + { + return Path.GetExtension(((FileStream)this.CurrentStream).Name); + } + return null; + } + + private string GetFilename() + { + if (this.CurrentStream.GetType() == typeof(FileStream)) + { + return Path.GetFileNameWithoutExtension(((FileStream)this.CurrentStream).Name); + } + return null; + } + + public void Dispose() + { + if (this.CurrentStream != null) + this.CurrentStream.Dispose(); + } + + } +} diff --git a/MMALSharp.Common/Handlers/VideoStreamCaptureHandler.cs b/MMALSharp.Common/Handlers/VideoStreamCaptureHandler.cs new file mode 100644 index 00000000..6d18cf13 --- /dev/null +++ b/MMALSharp.Common/Handlers/VideoStreamCaptureHandler.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MMALSharp.Handlers +{ + public class VideoStreamCaptureHandler : StreamCaptureHandler + { + public VideoStreamCaptureHandler(string directory, string extension) : base(directory, extension) { } + + public bool CanSplit() + { + if (this.CurrentStream.GetType() == typeof(FileStream)) + return true; + return false; + } + + public void Split() + { + if (this.CanSplit()) + { + this.NewFile(); + } + } + } +} diff --git a/MMALSharp.Common/MMALSharp.Common.csproj b/MMALSharp.Common/MMALSharp.Common.csproj new file mode 100644 index 00000000..57f88089 --- /dev/null +++ b/MMALSharp.Common/MMALSharp.Common.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8} + Library + Properties + MMALSharp.Common + MMALSharp.Common + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MMALSharp.Common/Properties/AssemblyInfo.cs b/MMALSharp.Common/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..abd49325 --- /dev/null +++ b/MMALSharp.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MMALSharp-Common")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MMALSharp-Common")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d8ca0bc9-ca3b-4ec1-9898-542a6f5346f8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MMALSharp/Utility/Helpers.cs b/MMALSharp.Common/Utility/Helpers.cs similarity index 100% rename from MMALSharp/Utility/Helpers.cs rename to MMALSharp.Common/Utility/Helpers.cs diff --git a/MMALSharp/Handlers/FFmpegCaptureHandler.cs b/MMALSharp.FFmpeg/Handlers/FFmpegCaptureHandler.cs similarity index 92% rename from MMALSharp/Handlers/FFmpegCaptureHandler.cs rename to MMALSharp.FFmpeg/Handlers/FFmpegCaptureHandler.cs index 7da86851..69035012 100644 --- a/MMALSharp/Handlers/FFmpegCaptureHandler.cs +++ b/MMALSharp.FFmpeg/Handlers/FFmpegCaptureHandler.cs @@ -8,6 +8,9 @@ namespace MMALSharp.Handlers { + /// + /// Currently experimental. Not working fully. + /// public class FFmpegCaptureHandler : ICaptureHandler { public Process MyProcess { get; set; } @@ -35,7 +38,7 @@ public FFmpegCaptureHandler(string streamName, string streamUrl) public bool CanSplit() { - throw new NotImplementedException(); + return false; } public string GetDirectory() @@ -67,9 +70,10 @@ public void Split() throw new NotImplementedException(); } - ~FFmpegCaptureHandler() + public void Dispose() { this.MyProcess.Close(); } + } } diff --git a/MMALSharp.FFmpeg/MMALSharp.FFmpeg.csproj b/MMALSharp.FFmpeg/MMALSharp.FFmpeg.csproj new file mode 100644 index 00000000..6ee7799a --- /dev/null +++ b/MMALSharp.FFmpeg/MMALSharp.FFmpeg.csproj @@ -0,0 +1,61 @@ + + + + + Debug + AnyCPU + {8F2E7EDB-4533-4FC5-A8AA-17F11302CC84} + Library + Properties + MMALSharp.FFmpeg + MMALSharp.FFmpeg + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + {d8ca0bc9-ca3b-4ec1-9898-542a6f5346f8} + MMALSharp.Common + + + + + \ No newline at end of file diff --git a/MMALSharp.FFmpeg/Properties/AssemblyInfo.cs b/MMALSharp.FFmpeg/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..e1ffff0a --- /dev/null +++ b/MMALSharp.FFmpeg/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MMALSharp-FFmpeg")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MMALSharp-FFmpeg")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8f2e7edb-4533-4fc5-a8aa-17f11302cc84")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MMALSharp.FFmpeg/VideoUtilities.cs b/MMALSharp.FFmpeg/VideoUtilities.cs new file mode 100644 index 00000000..a6ddbe01 --- /dev/null +++ b/MMALSharp.FFmpeg/VideoUtilities.cs @@ -0,0 +1,40 @@ +using MMALSharp.Handlers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MMALSharp.FFmpeg +{ + public static class VideoUtilities + { + /// + /// Useful for Timelapse captures. Enables you to convert a list of images associated with an ImageStreamCaptureHandler to a video + /// + /// + /// + public static void ImagesToVideo(this ImageStreamCaptureHandler result, string targetDirectory, int fps) + { + var process = new Process(); + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.FileName = "ffmpeg"; + + StringBuilder sb = new StringBuilder(); + result.ProcessedStreams.ForEach(c => sb.Append(string.Format("-i {0}", c))); + targetDirectory.TrimEnd('/'); + + if(fps == 0) + { + //Default to 25fps - FFmpeg defaults to this value if nothing is specified + fps = 25; + } + + process.StartInfo.Arguments = string.Format("-framerate {0} {1} {2}/out.avi", fps, sb.ToString(), targetDirectory); + process.Start(); + process.WaitForExit(); + } + } +} diff --git a/MMALSharp.sln b/MMALSharp.sln index 51e8bacc..1034a261 100644 --- a/MMALSharp.sln +++ b/MMALSharp.sln @@ -4,8 +4,22 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MMALSharp", "MMALSharp\MMALSharp.csproj", "{5B03E4B4-13AA-4EC8-8FD3-9DAA176EAA70}" + ProjectSection(ProjectDependencies) = postProject + {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8} = {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8} + {8F2E7EDB-4533-4FC5-A8AA-17F11302CC84} = {8F2E7EDB-4533-4FC5-A8AA-17F11302CC84} + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MMALSharpExample", "MMALSharpExample\MMALSharpExample.csproj", "{665C0F20-844A-44A8-9EF4-F359A607CAE6}" + ProjectSection(ProjectDependencies) = postProject + {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8} = {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MMALSharp.FFmpeg", "MMALSharp.FFmpeg\MMALSharp.FFmpeg.csproj", "{8F2E7EDB-4533-4FC5-A8AA-17F11302CC84}" + ProjectSection(ProjectDependencies) = postProject + {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8} = {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MMALSharp.Common", "MMALSharp.Common\MMALSharp.Common.csproj", "{D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +35,14 @@ Global {665C0F20-844A-44A8-9EF4-F359A607CAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {665C0F20-844A-44A8-9EF4-F359A607CAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {665C0F20-844A-44A8-9EF4-F359A607CAE6}.Release|Any CPU.Build.0 = Release|Any CPU + {8F2E7EDB-4533-4FC5-A8AA-17F11302CC84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F2E7EDB-4533-4FC5-A8AA-17F11302CC84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F2E7EDB-4533-4FC5-A8AA-17F11302CC84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F2E7EDB-4533-4FC5-A8AA-17F11302CC84}.Release|Any CPU.Build.0 = Release|Any CPU + {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8CA0BC9-CA3B-4EC1-9898-542A6F5346F8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MMALSharp/Components/MMALDownstreamComponent.cs b/MMALSharp/Components/MMALDownstreamComponent.cs index 5f2f9d55..0b67a6ba 100644 --- a/MMALSharp/Components/MMALDownstreamComponent.cs +++ b/MMALSharp/Components/MMALDownstreamComponent.cs @@ -26,8 +26,9 @@ public abstract unsafe class MMALDownstreamComponent : MMALComponentBase /// public ICaptureHandler Handler { get; set; } - public MMALDownstreamComponent(string name) : base(name) - { + public MMALDownstreamComponent(string name, ICaptureHandler handler) : base(name) + { + this.Handler = handler; } /// @@ -49,24 +50,51 @@ public void CreateConnection(MMALPortBase output) public virtual void ManagedCallback(MMALBufferImpl buffer, MMALPortBase port) { var data = buffer.GetBufferData(); - this.Handler.Process(data); + + if(this.Handler != null) + { + this.Handler.Process(data); + } } + /// + /// Enable the port with the specified port number. + /// + /// The output port number + /// The managed method to callback to from the native callback public void Start(int outputPortNumber, Action managedCallback) { + if(this.Handler != null && this.Handler.GetType() == typeof(StreamCaptureHandler)) + { + ((StreamCaptureHandler)this.Handler).NewFile(); + } + this.Outputs.ElementAt(outputPortNumber).EnablePort(managedCallback); } + /// + /// Enable the port specified. + /// + /// The output port + /// The managed method to callback to from the native callback public void Start(MMALPortBase port, Action managedCallback) { port.EnablePort(managedCallback); } + /// + /// Disable the port with the specified port number + /// + /// The output port number public void Stop(int outputPortNumber) { this.Outputs.ElementAt(outputPortNumber).DisablePort(); } + /// + /// Disable the specified port + /// + /// The output port public void Stop(MMALPortBase port) { port.DisablePort(); diff --git a/MMALSharp/Components/MMALEncoderComponent.cs b/MMALSharp/Components/MMALEncoderComponent.cs index e8125baa..6c035d3d 100644 --- a/MMALSharp/Components/MMALEncoderComponent.cs +++ b/MMALSharp/Components/MMALEncoderComponent.cs @@ -18,7 +18,7 @@ namespace MMALSharp.Components /// public abstract unsafe class MMALEncoderBase : MMALDownstreamComponent { - protected MMALEncoderBase(string encoderName) : base(encoderName) { } + protected MMALEncoderBase(string encoderName, ICaptureHandler handler) : base(encoderName, handler) { } /// /// Initializes the encoder component to allow processing to commence. Creates the same format between input/output port. @@ -159,7 +159,19 @@ internal void CleanEncoderPorts() } public override void Dispose() - { + { + if (MMALCameraConfig.Debug) + { + Console.WriteLine("Removing encoder"); + } + + this.Connection.Destroy(); + + MMALCamera.Instance.Encoders.Remove(this); + + //Remove any unmanaged resources held by the capture handler. + this.Handler.Dispose(); + base.Dispose(); } } @@ -210,7 +222,7 @@ public unsafe class MMALVideoEncoder : MMALEncoderBase /// public bool PrepareSplit { get; set; } - public MMALVideoEncoder(MMALEncoding encodingType, int bitrate, int framerate, int quality) : base(MMALParameters.MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER) + public MMALVideoEncoder(ICaptureHandler handler, MMALEncoding encodingType, int bitrate, int framerate, int quality) : base(MMALParameters.MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER, handler) { if (encodingType.EncodingVal > 0) { @@ -230,7 +242,7 @@ public MMALVideoEncoder(MMALEncoding encodingType, int bitrate, int framerate, i this.Initialize(); } - public MMALVideoEncoder(int quality) : base(MMALParameters.MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER) + public MMALVideoEncoder(ICaptureHandler handler, int quality) : base(MMALParameters.MMAL_COMPONENT_DEFAULT_VIDEO_ENCODER, handler) { this.Initialize(); } @@ -332,12 +344,10 @@ public override void Initialize() /// The buffer header we're currently processing /// The port we're currently processing on public override void ManagedCallback(MMALBufferImpl buffer, MMALPortBase port) - { - var data = buffer.GetBufferData(); - + { if (this.PrepareSplit && buffer.Properties.Any(c => c == MMALBufferProperties.MMAL_BUFFER_HEADER_FLAG_CONFIG)) { - this.Handler.Split(); + ((VideoStreamCaptureHandler)this.Handler).Split(); this.LastSplit = DateTime.Now; this.PrepareSplit = false; } @@ -357,7 +367,7 @@ public override void ManagedCallback(MMALBufferImpl buffer, MMALPortBase port) } } - this.Handler.Process(data); + base.ManagedCallback(buffer, port); } internal void ConfigureRateControl() @@ -478,7 +488,7 @@ private DateTime CalculateSplit() /// public unsafe class MMALVideoDecoder : MMALEncoderBase { - public MMALVideoDecoder() : base(MMALParameters.MMAL_COMPONENT_DEFAULT_VIDEO_DECODER) + public MMALVideoDecoder(ICaptureHandler handler) : base(MMALParameters.MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, handler) { this.Initialize(); } @@ -507,7 +517,7 @@ public unsafe class MMALImageEncoder : MMALEncoderBase /// public int Quality { get; set; } = 90; - public MMALImageEncoder(MMALEncoding encodingType, int quality) : base(MMALParameters.MMAL_COMPONENT_DEFAULT_IMAGE_ENCODER) + public MMALImageEncoder(ICaptureHandler handler, MMALEncoding encodingType, int quality) : base(MMALParameters.MMAL_COMPONENT_DEFAULT_IMAGE_ENCODER, handler) { if (encodingType.EncodingVal > 0) { @@ -522,7 +532,7 @@ public MMALImageEncoder(MMALEncoding encodingType, int quality) : base(MMALParam this.Initialize(); } - public MMALImageEncoder() : base(MMALParameters.MMAL_COMPONENT_DEFAULT_IMAGE_ENCODER) + public MMALImageEncoder(ICaptureHandler handler) : base(MMALParameters.MMAL_COMPONENT_DEFAULT_IMAGE_ENCODER, handler) { this.Initialize(); } @@ -628,7 +638,7 @@ internal unsafe void AddExifTag(ExifTag exifTag) /// public unsafe class MMALImageDecoder : MMALEncoderBase { - public MMALImageDecoder() : base(MMALParameters.MMAL_COMPONENT_DEFAULT_IMAGE_DECODER) + public MMALImageDecoder(ICaptureHandler handler) : base(MMALParameters.MMAL_COMPONENT_DEFAULT_IMAGE_DECODER, handler) { this.Initialize(); } diff --git a/MMALSharp/Components/MMALRendererComponent.cs b/MMALSharp/Components/MMALRendererComponent.cs index 53d83717..dc0cec68 100644 --- a/MMALSharp/Components/MMALRendererComponent.cs +++ b/MMALSharp/Components/MMALRendererComponent.cs @@ -12,7 +12,7 @@ namespace MMALSharp.Components /// public unsafe abstract class MMALRendererBase : MMALDownstreamComponent { - public MMALRendererBase(string name) : base(name) + public MMALRendererBase(string name) : base(name, null) { if (this.Ptr->InputNum > 0) { diff --git a/MMALSharp/Components/MMALSplitterComponent.cs b/MMALSharp/Components/MMALSplitterComponent.cs index 73316e84..5edb5d4f 100644 --- a/MMALSharp/Components/MMALSplitterComponent.cs +++ b/MMALSharp/Components/MMALSplitterComponent.cs @@ -9,7 +9,7 @@ namespace MMALSharp.Components { public class MMALSplitterComponent : MMALDownstreamComponent { - public MMALSplitterComponent() : base(MMALParameters.MMAL_COMPONENT_DEFAULT_VIDEO_SPLITTER) + public MMALSplitterComponent() : base(MMALParameters.MMAL_COMPONENT_DEFAULT_VIDEO_SPLITTER, null) { } diff --git a/MMALSharp/Handlers/ICaptureHandler.cs b/MMALSharp/Handlers/ICaptureHandler.cs deleted file mode 100644 index c549ede4..00000000 --- a/MMALSharp/Handlers/ICaptureHandler.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MMALSharp.Handlers -{ - public interface ICaptureHandler - { - /// - /// Used to process the byte array containing our image data - /// - /// A byte array containing image data - void Process(byte[] data); - /// - /// Indicates whether segmented video recording is possible with this handler - /// - /// - bool CanSplit(); - /// - /// Used to handle segmented video recording - /// - void Split(); - /// - /// Gets the directory of a stream based handler - /// - /// - string GetDirectory(); - /// - /// Used for any further processing once we have completed capture - /// - void PostProcess(); - } -} diff --git a/MMALSharp/Handlers/StreamCaptureResult.cs b/MMALSharp/Handlers/StreamCaptureResult.cs deleted file mode 100644 index 7601d082..00000000 --- a/MMALSharp/Handlers/StreamCaptureResult.cs +++ /dev/null @@ -1,94 +0,0 @@ -using MMALSharp.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MMALSharp.Handlers -{ - /// - /// Processes the image data to a stream. - /// - public class StreamCaptureResult : ICaptureHandler - { - private Stream _stream; - private int _processed; - private string _origFilename; - - public StreamCaptureResult(Stream stream) - { - this._stream = stream; - this._origFilename = this.GetFilename(); - } - - public void Process(byte[] data) - { - this._processed += data.Length; - - if (this._stream.CanWrite) - this._stream.Write(data, 0, data.Length); - else - throw new PiCameraError("Stream not writable."); - - if (MMALCameraConfig.Debug) - Console.WriteLine("Currently processed: " + Helpers.ConvertBytesToMegabytes(this._processed)); - } - - public void PostProcess() - { - Console.WriteLine(string.Format("Successfully processed {0}", Helpers.ConvertBytesToMegabytes(this._processed))); - } - - public bool CanSplit() - { - if(this._stream.GetType() == typeof(FileStream)) - return true; - return false; - } - - public void Split() - { - if(this.CanSplit()) - { - this._stream.Dispose(); - this._stream = File.Create(this.GetDirectory() + "/" + this._origFilename + " " + DateTime.Now.ToString("dd-MMM-yy HH-mm-ss") + this.GetExtension()); - } - } - - public string GetDirectory() - { - if(this._stream.GetType() == typeof(FileStream)) - { - return Path.GetDirectoryName(((FileStream)this._stream).Name); - } - return null; - } - - private string GetExtension() - { - if (this._stream.GetType() == typeof(FileStream)) - { - return Path.GetExtension(((FileStream)this._stream).Name); - } - return null; - } - - private string GetFilename() - { - if (this._stream.GetType() == typeof(FileStream)) - { - return Path.GetFileNameWithoutExtension(((FileStream)this._stream).Name); - } - return null; - } - - ~StreamCaptureResult() - { - if(this._stream != null) - this._stream.Dispose(); - } - - } -} diff --git a/MMALSharp/MMALCamera.cs b/MMALSharp/MMALCamera.cs index f8fe3478..90876885 100644 --- a/MMALSharp/MMALCamera.cs +++ b/MMALSharp/MMALCamera.cs @@ -1,14 +1,9 @@ using MMALSharp.Components; -using MMALSharp.Handlers; using MMALSharp.Native; using MMALSharp.Utility; using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; using System.Threading.Tasks; namespace MMALSharp @@ -16,7 +11,7 @@ namespace MMALSharp /// /// This class provides an interface to the Raspberry Pi camera module. /// - public sealed class MMALCamera : IDisposable + public sealed class MMALCamera { /// /// Reference to the camera component @@ -38,14 +33,10 @@ public sealed class MMALCamera : IDisposable /// public MMALSplitterComponent Splitter { get; set; } - private static readonly MMALCamera instance = new MMALCamera(); - - // Explicit static constructor to tell C# compiler - // not to mark type as beforefieldinit - static MMALCamera() - { - } + private static readonly Lazy lazy = new Lazy(() => new MMALCamera()); + public static MMALCamera Instance { get { return lazy.Value; } } + private MMALCamera() { BcmHost.bcm_host_init(); @@ -54,18 +45,10 @@ private MMALCamera() this.Encoders = new List(); } - public static MMALCamera Instance - { - get - { - return instance; - } - } - /// /// Begin capture on one of the camera's output ports. /// - /// + /// An output port of the camera component public void StartCapture(MMALPortImpl port) { if (port == this.Camera.StillPort || this.Encoders.Any(c => c.Enabled)) @@ -75,7 +58,7 @@ public void StartCapture(MMALPortImpl port) /// /// Stop capture on one of the camera's output ports /// - /// + /// An output port of the camera component public void StopCapture(MMALPortImpl port) { if (port == this.Camera.StillPort || this.Encoders.Any(c => c.Enabled)) @@ -99,13 +82,8 @@ public void ForceStop(MMALPortImpl port) /// A timeout to stop the video capture /// Used for Segmented video mode /// - public async Task TakeVideo(MMALPortImpl connPort, ICaptureHandler handler, DateTime? timeout = null, Split split = null) - { - if (handler == null) - { - throw new PiCameraError("Handler cannot be null"); - } - + public async Task TakeVideo(MMALPortImpl connPort, DateTime? timeout = null, Split split = null) + { var encoder = this.Encoders.Where(c => c.Connection != null && c.Connection.OutputPort == connPort).FirstOrDefault(); if (encoder == null || encoder.GetType() != typeof(MMALVideoEncoder)) @@ -113,8 +91,6 @@ public async Task TakeVideo(MMALPortImpl connPort, ICaptureHandler handler, Date throw new PiCameraError("No video encoder currently attached to output port specified"); } - encoder.Handler = handler; - if (!encoder.Connection.Enabled) { encoder.Connection.Enable(); @@ -139,124 +115,36 @@ public async Task TakeVideo(MMALPortImpl connPort, ICaptureHandler handler, Date var outputPort = 0; - //Enable the video encoder output port. - encoder.Start(outputPort, encoder.ManagedCallback); - - encoder.Outputs.ElementAt(outputPort).Trigger = new Nito.AsyncEx.AsyncCountdownEvent(1); - - ((MMALVideoPort)encoder.Outputs.ElementAt(outputPort)).Timeout = timeout; - ((MMALVideoEncoder)encoder).Split = split; + try + { + //Enable the video encoder output port. + encoder.Start(outputPort, encoder.ManagedCallback); - this.StartCapture(this.Camera.VideoPort); - - await encoder.Outputs.ElementAt(outputPort).Trigger.WaitAsync(); - - //Wait until the process is complete. - this.StopCapture(this.Camera.VideoPort); + encoder.Outputs.ElementAt(outputPort).Trigger = new Nito.AsyncEx.AsyncCountdownEvent(1); - //Disable the image encoder output port. - encoder.Stop(outputPort); + ((MMALVideoPort)encoder.Outputs.ElementAt(outputPort)).Timeout = timeout; + ((MMALVideoEncoder)encoder).Split = split; - //Close open connections. - encoder.Connection.Disable(); - encoder.CleanEncoderPorts(); + this.StartCapture(this.Camera.VideoPort); - encoder.Handler.PostProcess(); - } + await encoder.Outputs.ElementAt(outputPort).Trigger.WaitAsync(); - /// - /// Captures a single image from the camera's still port. - /// Initializes a standalone MMALImageEncoder using the provided encodingType and quality. - /// - /// The capture handler for this capture method - /// The image encoding type - /// Quality of the image (valid only for JPEG) - /// Include raw bayer metadeta in the capture - /// Specify whether to include EXIF tags in the capture - /// Custom EXIF tags to use in the capture - /// - public async Task TakeSinglePicture(ICaptureHandler handler, MMALEncoding encodingType, int quality = 0, bool raw = false, bool useExif = true, params ExifTag[] exifTags) - { - var camPreviewPort = this.Camera.PreviewPort; - var camVideoPort = this.Camera.VideoPort; - var camStillPort = this.Camera.StillPort; + //Wait until the process is complete. + this.StopCapture(this.Camera.VideoPort); - if (handler == null) - { - throw new PiCameraError("Handler cannot be null"); - } + //Disable the image encoder output port. + encoder.Stop(outputPort); - if (this.Encoders.Any(c => c.Connection != null && c.Connection.OutputPort == this.Camera.StillPort && c.GetType() == typeof(MMALImageEncoder))) - { - //Reuse if an Image encoder is already connected to the Still camera port - await TakePicture(this.Camera.StillPort, this.Camera.StillPort, handler, useExif, raw, exifTags); + //Close open connections. + encoder.Connection.Disable(); + encoder.CleanEncoderPorts(); } - else + finally { - if(encodingType == null) - { - throw new PiCameraError("Encoding type cannot be null"); - } - - Console.WriteLine("Preparing to take picture"); - using (var encoder = new MMALImageEncoder(encodingType, quality)) - { - if (useExif) - { - ((MMALImageEncoder)encoder).AddExifTags(exifTags); - } - - //Create connections - if (this.Preview == null) - { - Helpers.PrintWarning("Preview port does not have a Render component configured. Resulting image will be affected."); - } - else - { - if (this.Preview.Connection == null) - { - this.Preview.CreateConnection(camPreviewPort); - } - } - - encoder.Handler = handler; - - encoder.CreateConnection(camStillPort); - - if (raw) - { - camStillPort.SetRawCapture(true); - } - - if (MMALCameraConfig.EnableAnnotate) - { - encoder.AnnotateImage(); - } - - int outputPort = 0; - - //Enable the image encoder output port. - encoder.Start(outputPort, encoder.ManagedCallback); - - this.StartCapture(camStillPort); - - //Wait until the process is complete. - encoder.Outputs.ElementAt(outputPort).Trigger = new Nito.AsyncEx.AsyncCountdownEvent(1); - await encoder.Outputs.ElementAt(outputPort).Trigger.WaitAsync(); - - this.StopCapture(camStillPort); - - //Disable the image encoder output port. - encoder.Stop(outputPort); - - //Close open connections. - encoder.Connection.Destroy(); - - encoder.Handler.PostProcess(); - } - } + encoder.Handler.PostProcess(); + } } - + /// /// Captures a single image from the output port specified. Expects an MMALImageEncoder to be attached. /// @@ -267,23 +155,18 @@ public async Task TakeSinglePicture(ICaptureHandler handler, MMALEncoding encodi /// Specify whether to include EXIF tags in the capture /// Custom EXIF tags to use in the capture /// - public async Task TakePicture(MMALPortImpl cameraPort, MMALPortImpl connPort, ICaptureHandler handler, bool raw = false, bool useExif = true, params ExifTag[] exifTags) + public async Task TakePicture(MMALPortImpl cameraPort, MMALPortImpl connPort, bool raw = false, bool useExif = true, params ExifTag[] exifTags) { Console.WriteLine("Preparing to take picture"); //Find the encoder/decoder which is connected to the output port specified. var encoder = this.Encoders.Where(c => c.Connection != null && c.Connection.OutputPort == connPort).FirstOrDefault(); - + if (encoder == null || encoder.GetType() != typeof(MMALImageEncoder)) - throw new PiCameraError("No image encoder currently attached to output port specified"); - - if (handler == null) { - throw new PiCameraError("Handler cannot be null"); + throw new PiCameraError("No image encoder currently attached to output port specified"); } - - encoder.Handler = handler; - + if (!encoder.Connection.Enabled) { encoder.Connection.Enable(); @@ -317,105 +200,68 @@ public async Task TakePicture(MMALPortImpl cameraPort, MMALPortImpl connPort, IC var outputPort = 0; - //Enable the image encoder output port. - encoder.Start(outputPort, encoder.ManagedCallback); - - this.StartCapture(cameraPort); - - //Wait until the process is complete. - encoder.Outputs.ElementAt(outputPort).Trigger = new Nito.AsyncEx.AsyncCountdownEvent(1); - await encoder.Outputs.ElementAt(outputPort).Trigger.WaitAsync(); + //Enable the image encoder output port. + try + { + encoder.Start(outputPort, encoder.ManagedCallback); - this.StopCapture(cameraPort); + this.StartCapture(cameraPort); - //Disable the image encoder output port. - encoder.Stop(outputPort); + //Wait until the process is complete. + encoder.Outputs.ElementAt(outputPort).Trigger = new Nito.AsyncEx.AsyncCountdownEvent(1); + await encoder.Outputs.ElementAt(outputPort).Trigger.WaitAsync(); - //Close open connections. - encoder.Connection.Disable(); - encoder.CleanEncoderPorts(); + this.StopCapture(cameraPort); - encoder.Handler.PostProcess(); - } + //Disable the image encoder output port. + encoder.Stop(outputPort); - /// - /// Takes a number of images as specified by the iterations parameter. - /// - /// The directory to save our image - /// The file extension of our image - /// Number of pictures to take - /// The encoding type to use - /// The quality of the image (Valid for JPEG) - /// Include raw bayer metadeta in the capture - /// Specify whether to include EXIF tags in the capture - /// Custom EXIF tags to use in the capture - public async Task TakePictureIterative(string directory, string extension, int iterations, MMALEncoding encodingType, int quality = 0, bool raw = false, bool useExif = true, params ExifTag[] exifTags) - { - if(encodingType == null) - { - throw new PiCameraError("Encoding type cannot be null"); + //Close open connections. + encoder.Connection.Disable(); + encoder.CleanEncoderPorts(); } - - for (int i = 0; i < iterations; i++) + finally { - var filename = (directory.EndsWith("/") ? directory : directory + "/") + DateTime.Now.ToString("dd-MMM-yy HH-mm-ss") + (extension.StartsWith(".") ? extension : "." + extension); - - await TakeSinglePicture(new StreamCaptureResult(File.Create(filename)), encodingType, quality, raw, useExif, exifTags); - } + encoder.Handler.PostProcess(); + } } - + /// /// Takes images until the moment specified in the timeout parameter has been met. /// - /// The directory to save our image - /// The file extension of our image - /// Take images until this timeout is hit - /// The encoding type to use - /// The quality of the image (Valid for JPEG) + /// The port that is currently capturing images (Still or Video) + /// The port our encoder is attached to + /// Take images until this timeout is hit /// Include raw bayer metadeta in the capture /// Specify whether to include EXIF tags in the capture /// Custom EXIF tags to use in the capture - public async Task TakePictureTimeout(string directory, string extension, DateTime timeout, MMALEncoding encodingType, int quality = 0, bool raw = false, bool useExif = true, params ExifTag[] exifTags) - { - if (encodingType == null) - { - throw new PiCameraError("Encoding type cannot be null"); - } - + public async Task TakePictureTimeout(MMALPortImpl cameraPort, MMALPortImpl connPort, DateTime timeout, bool raw = false, bool useExif = true, params ExifTag[] exifTags) + { while (DateTime.Now.CompareTo(timeout) < 0) - { - var filename = (directory.EndsWith("/") ? directory : directory + "/") + DateTime.Now.ToString("dd-MMM-yy HH-mm-ss") + (extension.StartsWith(".") ? extension : "." + extension); - - await TakeSinglePicture(new StreamCaptureResult(File.Create(filename)), encodingType, quality, raw, useExif, exifTags); + { + await TakePicture(cameraPort, connPort, raw, useExif, exifTags); } } /// /// Takes a timelapse image. You can specify the interval between each image taken and also when the operation should finish. /// - /// The directory to save our image - /// The file extension of our image - /// Specifies settings for the Timelapse - /// The encoding type to use - /// The quality of the image (Valid for JPEG) + /// The port that is currently capturing images (Still or Video) + /// The port our encoder is attached to + /// Specifies settings for the Timelapse /// Include raw bayer metadeta in the capture /// Specify whether to include EXIF tags in the capture /// Custom EXIF tags to use in the capture /// - public async Task TakePictureTimelapse(string directory, string extension, Timelapse tl, MMALEncoding encodingType, int quality = 0, bool raw = false, bool useExif = true, params ExifTag[] exifTags) - { - if (encodingType == null) - { - throw new PiCameraError("Encoding type cannot be null"); - } - + public async Task TakePictureTimelapse(MMALPortImpl cameraPort, MMALPortImpl connPort, Timelapse tl, bool raw = false, bool useExif = true, params ExifTag[] exifTags) + { int interval = 0; if(tl == null) { throw new PiCameraError("Timelapse object null. This must be initialized for Timelapse mode"); } - + while (DateTime.Now.CompareTo(tl.Timeout) < 0) { switch (tl.Mode) @@ -432,10 +278,8 @@ public async Task TakePictureTimelapse(string directory, string extension, Timel } await Task.Delay(interval); - - var filename = (directory.EndsWith("/") ? directory : directory + "/") + DateTime.Now.ToString("dd-MMM-yy HH-mm-ss") + (extension.StartsWith(".") ? extension : "." + extension); - - await TakeSinglePicture(new StreamCaptureResult(File.Create(filename)), encodingType, quality, raw, useExif, exifTags); + + await TakePicture(cameraPort, connPort, raw, useExif, exifTags); } } @@ -474,12 +318,12 @@ public MMALCamera CreateSplitterComponent() /// The output port to attach to /// public MMALCamera AddEncoder(MMALEncoderBase encoder, MMALPortImpl outputPort) - { + { if (MMALCameraConfig.Debug) { Console.WriteLine("Adding encoder"); } - + this.RemoveEncoder(outputPort); encoder.CreateConnection(outputPort); @@ -569,31 +413,32 @@ public MMALCamera ConfigureCamera() return this; } - - public void Dispose() + + /// + /// Cleans up any unmanaged resources. It is intended for this method to be run when no more activity is to be done on the camera. + /// + public void Cleanup() { if (MMALCameraConfig.Debug) { Console.WriteLine("Destroying final components"); } - + this.Encoders.ForEach(c => c.Dispose()); if (this.Preview != null) { this.Preview.Dispose(); } - + if (this.Camera != null) { this.Camera.Dispose(); } - + BcmHost.bcm_host_deinit(); } + } - - - - + } diff --git a/MMALSharp/MMALSharp.csproj b/MMALSharp/MMALSharp.csproj index 1b594ba5..67fb0b3a 100644 --- a/MMALSharp/MMALSharp.csproj +++ b/MMALSharp/MMALSharp.csproj @@ -54,15 +54,11 @@ - - - - - + @@ -87,13 +83,22 @@ - Designer + + + {d8ca0bc9-ca3b-4ec1-9898-542a6f5346f8} + MMALSharp.Common + + + {8f2e7edb-4533-4fc5-a8aa-17f11302cc84} + MMALSharp.FFmpeg + +