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

Motion detection plug-in model and analysis #175

Merged
merged 15 commits into from
Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/MMALSharp.Common/MMALSharp.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
<VersionPrefix>0.7.0</VersionPrefix>
<CodeAnalysisRuleSet>..\..\StyleCop.Analyzers.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<ProjectGuid>{65a1440e-72e1-4943-b469-5cfba8cb5633}</ProjectGuid> <!--Project guid for Sonar-->
<ProjectGuid>{65a1440e-72e1-4943-b469-5cfba8cb5633}</ProjectGuid>
<!--Project guid for Sonar-->
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='AnyCPU'">
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
Expand Down
2 changes: 1 addition & 1 deletion src/MMALSharp.FFmpeg/Handlers/FFmpegCaptureHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public static ExternalProcessCaptureHandler RawVideoToMP4(string directory, stri
{
Filename = "ffmpeg",
Arguments = $"-framerate {fps} -i - -b:v {bitrate}k -c copy -movflags +frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov {directory.TrimEnd()}/{filename}.mp4",
EchoOutput = true,
EchoOutput = echoOutput,
DrainOutputDelayMs = 500, // default
TerminationSignals = ExternalProcessCaptureHandlerOptions.SignalsFFmpeg
};
Expand Down
31 changes: 28 additions & 3 deletions src/MMALSharp.FFmpeg/Handlers/VLCCaptureHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,50 @@ public static class VLCCaptureHandler
private static readonly string _VLCInternalMimeBoundaryName = "7b3cc56e5f51db803f790dad720ed50a";

/// <summary>
/// Listens for a request on the given port and begins streaming MJPEG images when a client connects.
/// Listens for a request on the given port and begins streaming MJPEG images when a client connects. Requires h.264 encoded I420 (YUV420p) as input.
/// </summary>
/// <param name="listenPort">The port to listen on. Defaults to 8554.</param>
/// <param name="echoOutput">Whether to echo stdout and stderr to the console or suppress it. Defaults to true.</param>
/// <param name="maxBitrate">Maximum output bitrate. If source data is available at a higher bitrate, VLC caps to this. Defaults to 2500 (25Mbps).</param>
/// <param name="maxFps">Maximum output framerate. If source data is available at a higher framerate, VLC caps to this. Defaults to 20.</param>
/// <returns>An initialized instance of <see cref="ExternalProcessCaptureHandler"/></returns>
public static ExternalProcessCaptureHandler StreamMJPEG(int listenPort = 8554, bool echoOutput = true, int maxBitrate = 2500, int maxFps = 20)
public static ExternalProcessCaptureHandler StreamH264asMJPEG(int listenPort = 8554, bool echoOutput = true, int maxBitrate = 2500, int maxFps = 20)
{
var opts = new ExternalProcessCaptureHandlerOptions
{
Filename = "cvlc",
Arguments = $"stream:///dev/stdin --sout \"#transcode{{vcodec=mjpg,vb={maxBitrate},fps={maxFps},acodec=none}}:standard{{access=http{{mime=multipart/x-mixed-replace;boundary=--{_VLCInternalMimeBoundaryName}}},mux=mpjpeg,dst=:{listenPort}/}}\" :demux=h264",
Arguments = $"stream:///dev/stdin --sout \"#transcode{{vcodec=mjpg,vb={maxBitrate},fps={maxFps},acodec=none}}:standard{{access=http{{mime=multipart/x-mixed-replace;boundary={_VLCInternalMimeBoundaryName}}},mux=mpjpeg,dst=:{listenPort}/}}\" :demux=h264",
EchoOutput = echoOutput,
DrainOutputDelayMs = 500, // default
TerminationSignals = ExternalProcessCaptureHandlerOptions.SignalsVLC
};

return new ExternalProcessCaptureHandler(opts);
}

/// <summary>
/// Listens for a request on the given port and begins streaming MJPEG images when a client connects. Requires raw RGB24 frames as input.
/// </summary>
/// <param name="width">The width of the raw frames. Defaults to 640.</param>
/// <param name="height">The height of the raw frames. Defaults to 480.</param>
/// <param name="fps">Expected FPS of the raw frames. Defaults to 24.</param>
/// <param name="listenPort">The port to listen on. Defaults to 8554.</param>
/// <param name="echoOutput">Whether to echo stdout and stderr to the console or suppress it. Defaults to true.</param>
/// <param name="maxBitrate">Maximum output bitrate. If source data is available at a higher bitrate, VLC caps to this. Defaults to 2500 (25Mbps).</param>
/// <param name="maxFps">Maximum output framerate. If source data is available at a higher framerate, VLC caps to this. Defaults to 20.</param>
/// <returns>An initialized instance of <see cref="ExternalProcessCaptureHandler"/></returns>
public static ExternalProcessCaptureHandler StreamRawRGB24asMJPEG(int width = 640, int height = 480, int fps = 24, int listenPort = 8554, bool echoOutput = true, int maxBitrate = 2500, int maxFps = 20)
{
var opts = new ExternalProcessCaptureHandlerOptions
{
Filename = "/bin/bash",
EchoOutput = true,
Arguments = $"-c \"ffmpeg -hide_banner -f rawvideo -c:v rawvideo -pix_fmt rgb24 -s:v {width}x{height} -r {fps} -i - -f h264 -c:v libx264 -preset ultrafast -tune zerolatency -vf format=yuv420p - | cvlc stream:///dev/stdin --sout '#transcode{{vcodec=mjpg,vb={maxBitrate},fps={maxFps},acodec=none}}:standard{{access=http{{mime=multipart/x-mixed-replace;boundary={_VLCInternalMimeBoundaryName}}},mux=mpjpeg,dst=:{listenPort}/}}' :demux=h264\"",
DrainOutputDelayMs = 500, // default = 500
TerminationSignals = ExternalProcessCaptureHandlerOptions.SignalsFFmpeg
};

return new ExternalProcessCaptureHandler(opts);
}
}
}
3 changes: 2 additions & 1 deletion src/MMALSharp.FFmpeg/MMALSharp.FFmpeg.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
<VersionPrefix>0.7.0</VersionPrefix>
<CodeAnalysisRuleSet>..\..\StyleCop.Analyzers.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<ProjectGuid>{0600c674-e587-4267-89f3-b52ae9591f80}</ProjectGuid> <!--Project guid for Sonar-->
<ProjectGuid>{0600c674-e587-4267-89f3-b52ae9591f80}</ProjectGuid>
<!--Project guid for Sonar-->
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='AnyCPU'">
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,6 @@ public void PostProcess()
public string GetDirectory()
=> throw new NotImplementedException();

/// <summary>
/// Not used.
/// </summary>
/// <param name="allocSize">N/A.</param>
/// <returns>A NotImplementedException.</returns>
/// <exception cref="NotImplementedException"></exception>
public ProcessResult Process(uint allocSize)
=> throw new NotImplementedException();

/// <summary>
/// Writes frame data to the StandardInput stream for processing.
/// </summary>
Expand Down
52 changes: 43 additions & 9 deletions src/MMALSharp.Processing/Handlers/FrameBufferCaptureHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ namespace MMALSharp.Handlers
/// </summary>
public class FrameBufferCaptureHandler : MemoryStreamCaptureHandler, IMotionCaptureHandler, IVideoCaptureHandler
{
private MotionConfig _motionConfig;
private bool _detectingMotion;
private FrameDiffAnalyser _motionAnalyser;
private FrameDiffDriver _driver;

private bool _waitForFullFrame = true;
private bool _writeFrameRequested = false;
Expand All @@ -45,6 +44,19 @@ public FrameBufferCaptureHandler()
: base()
{ }

/// <summary>
/// Creates a new <see cref="FrameBufferCaptureHandler"/> configured for motion detection using a raw video stream
/// where MMALStandalone.Instance is used (such as processing a pre-recorded file) rather than camera-based processing.
/// </summary>
/// <param name="motionConfig">The motion configuration.</param>
/// <param name="onDetect">A callback for when motion is detected.</param>
public FrameBufferCaptureHandler(MotionConfig motionConfig, Action onDetect)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this additional constructor is used to replicate what MMALCamera.WithMotionDetection() does?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By funny coincidence I was just looking at that myself, and I don't think it is needed. There was a point where I was trying to avoid modifying MotionConfig and I think it's left over from that. I will straighten it out.

Copy link
Collaborator Author

@MV10 MV10 Sep 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually that constructor was useful when doing nothing but visualizing the motion detection analysis. Used in a simple configuration like this:

using (var shell = new ExternalProcessCaptureHandler(raw_to_mjpeg_stream))
using (var motion = new FrameBufferCaptureHandler(motionConfig, null))
using (var resizer = new MMALIspComponent())
{
    motionAlgorithm.EnableAnalysis(shell);

    resizer.ConfigureOutputPort<VideoPort>(0, new MMALPortConfig(MMALEncoding.RGB24, MMALEncoding.RGB24, width: 640, height: 480), motion);
    cam.Camera.VideoPort.ConnectTo(resizer);

    await cameraWarmupDelay(cam);
    var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(seconds));
    await Task.WhenAll(new Task[]{
            shell.ProcessExternalAsync(timeout.Token),
            cam.ProcessAsync(cam.Camera.VideoPort, timeout.Token),
        }).ConfigureAwait(false);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see I do explain one usage in the constructor summary, although it could be better, it does now also work with the camera (the example above is for streaming). I'll update the summary.

Creates a new configured for motion detection using a raw video stream where MMALStandalone.Instance is used (such as processing a pre-recorded file) rather than camera-based processing.

: base()
{
_driver = new FrameDiffDriver(motionConfig, onDetect);
_detectingMotion = true;
}

/// <summary>
/// Target directory when <see cref="WriteFrame"/> is invoked without a directory argument.
/// </summary>
Expand Down Expand Up @@ -99,7 +111,7 @@ public override void Process(ImageContext context)

if (_detectingMotion)
{
_motionAnalyser.Apply(context);
_driver.Apply(context);
}

// accumulate frame data in the underlying memory stream
Expand All @@ -122,22 +134,35 @@ public override void Process(ImageContext context)
/// <inheritdoc />
public void ConfigureMotionDetection(MotionConfig config, Action onDetect)
{
_motionConfig = config;
_motionAnalyser = new FrameDiffAnalyser(config, onDetect);
_driver = new FrameDiffDriver(config, onDetect);
this.EnableMotionDetection();
}

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

/// <inheritdoc />
public void DisableMotionDetection()
public void DisableMotionDetection(bool disableCallbackOnly = false)
{
_detectingMotion = false;
if(disableCallbackOnly)
{
_driver.OnDetectEnabled = false;
}
else
{
_detectingMotion = false;
}
}

/// <inheritdoc />
Expand All @@ -158,5 +183,14 @@ private void WriteStreamToFile()
this.MostRecentFilename = filename;
this.MostRecentPathname = pathname;
}

// This is used for temporary local-development performance testing. See the
// commentedlines before and inside FrameDiffDriver around the Apply method.
// public override void Dispose()
// {
// long perf = (long)((float)_driver.totalElapsed / _driver.frameCounter);
// Console.WriteLine($"{perf} ms/frame, total {_driver.frameCounter} frames");
// base.Dispose();
// }
}
}
3 changes: 2 additions & 1 deletion src/MMALSharp.Processing/Handlers/IMotionCaptureHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public interface IMotionCaptureHandler
/// <summary>
/// Disables motion detection. When configured, this will instruct the capture handler not to detect motion.
/// </summary>
void DisableMotionDetection();
/// <param name="disableCallbackOnly">When true, motion detection will continue but the OnDetect callback will not be invoked.</param>
void DisableMotionDetection(bool disableCallbackOnly = false);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user calls this method and wants to re-enable the callback, how do they do this? Am I right in thinking they just modify OnDetectEnabled against a FrameDiffDriver instance?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling EnableMotionDetection will re-enable the callback. I will add that to the parameter docs.

}
}
3 changes: 2 additions & 1 deletion src/MMALSharp.Processing/MMALSharp.Processing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
<VersionPrefix>0.7.0</VersionPrefix>
<CodeAnalysisRuleSet>..\..\StyleCop.Analyzers.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<ProjectGuid>{dabc9991-56ad-4235-ba86-63def12c261a}</ProjectGuid> <!--Project guid for Sonar-->
<ProjectGuid>{dabc9991-56ad-4235-ba86-63def12c261a}</ProjectGuid>
<!--Project guid for Sonar-->
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand Down
Loading