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 + +