2019-12-03 18:43:54 +01:00
using Unosquare.Swan.Abstractions ;
using System ;
using Unosquare.Swan.Components ;
using System.IO ;
using System.Threading ;
using System.Threading.Tasks ;
namespace Unosquare.RaspberryIO.Camera {
/// <summary>
/// The Raspberry Pi's camera controller wrapping raspistill and raspivid programs.
/// This class is a singleton
/// </summary>
public class CameraController : SingletonBase < CameraController > {
#region Private Declarations
private static readonly ManualResetEventSlim OperationDone = new ManualResetEventSlim ( true ) ;
private static readonly Object SyncRoot = new Object ( ) ;
private static CancellationTokenSource _videoTokenSource = new CancellationTokenSource ( ) ;
private static Task < Task > _videoStreamTask ;
#endregion
#region Properties
2019-02-17 14:08:57 +01:00
/// <summary>
2019-12-03 18:43:54 +01:00
/// Gets a value indicating whether the camera module is busy.
2019-02-17 14:08:57 +01:00
/// </summary>
2019-12-03 18:43:54 +01:00
/// <value>
/// <c>true</c> if this instance is busy; otherwise, <c>false</c>.
/// </value>
public Boolean IsBusy = > OperationDone . IsSet = = false ;
#endregion
#region Image Capture Methods
/// <summary>
/// Captures an image asynchronously.
/// </summary>
/// <param name="settings">The settings.</param>
/// <param name="ct">The ct.</param>
/// <returns>The image bytes</returns>
/// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public async Task < Byte [ ] > CaptureImageAsync ( CameraStillSettings settings , CancellationToken ct = default ) {
if ( Instance . IsBusy ) {
throw new InvalidOperationException ( "Cannot use camera module because it is currently busy." ) ;
}
if ( settings . CaptureTimeoutMilliseconds < = 0 ) {
throw new ArgumentException ( $"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than 0" ) ;
}
try {
OperationDone . Reset ( ) ;
MemoryStream output = new MemoryStream ( ) ;
Int32 exitCode = await ProcessRunner . RunProcessAsync (
settings . CommandName ,
settings . CreateProcessArguments ( ) ,
( data , proc ) = > output . Write ( data , 0 , data . Length ) ,
null ,
true ,
ct ) ;
return exitCode ! = 0 ? new Byte [ ] { } : output . ToArray ( ) ;
} finally {
OperationDone . Set ( ) ;
}
}
/// <summary>
/// Captures an image.
/// </summary>
/// <param name="settings">The settings.</param>
/// <returns>The image bytes</returns>
public Byte [ ] CaptureImage ( CameraStillSettings settings ) = > this . CaptureImageAsync ( settings ) . GetAwaiter ( ) . GetResult ( ) ;
/// <summary>
/// Captures a JPEG encoded image asynchronously at 90% quality.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="ct">The ct.</param>
/// <returns>The image bytes</returns>
public Task < Byte [ ] > CaptureImageJpegAsync ( Int32 width , Int32 height , CancellationToken ct = default ) {
CameraStillSettings settings = new CameraStillSettings {
CaptureWidth = width ,
CaptureHeight = height ,
CaptureJpegQuality = 90 ,
CaptureTimeoutMilliseconds = 300 ,
} ;
return this . CaptureImageAsync ( settings , ct ) ;
}
/// <summary>
/// Captures a JPEG encoded image at 90% quality.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns>The image bytes</returns>
public Byte [ ] CaptureImageJpeg ( Int32 width , Int32 height ) = > this . CaptureImageJpegAsync ( width , height ) . GetAwaiter ( ) . GetResult ( ) ;
#endregion
#region Video Capture Methods
/// <summary>
/// Opens the video stream with a timeout of 0 (running indefinitely) at 1080p resolution, variable bitrate and 25 FPS.
/// No preview is shown
/// </summary>
/// <param name="onDataCallback">The on data callback.</param>
/// <param name="onExitCallback">The on exit callback.</param>
public void OpenVideoStream ( Action < Byte [ ] > onDataCallback , Action onExitCallback = null ) {
CameraVideoSettings settings = new CameraVideoSettings {
CaptureTimeoutMilliseconds = 0 ,
CaptureDisplayPreview = false ,
CaptureWidth = 1920 ,
CaptureHeight = 1080
} ;
this . OpenVideoStream ( settings , onDataCallback , onExitCallback ) ;
}
/// <summary>
/// Opens the video stream with the supplied settings. Capture Timeout Milliseconds has to be 0 or greater
/// </summary>
/// <param name="settings">The settings.</param>
/// <param name="onDataCallback">The on data callback.</param>
/// <param name="onExitCallback">The on exit callback.</param>
/// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception>
/// <exception cref="ArgumentException">CaptureTimeoutMilliseconds</exception>
public void OpenVideoStream ( CameraVideoSettings settings , Action < Byte [ ] > onDataCallback , Action onExitCallback ) {
if ( Instance . IsBusy ) {
throw new InvalidOperationException ( "Cannot use camera module because it is currently busy." ) ;
}
if ( settings . CaptureTimeoutMilliseconds < 0 ) {
throw new ArgumentException ( $"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than or equal to 0" ) ;
}
try {
OperationDone . Reset ( ) ;
_videoStreamTask = Task . Factory . StartNew ( ( ) = > VideoWorkerDoWork ( settings , onDataCallback , onExitCallback ) , _videoTokenSource . Token ) ;
} catch {
OperationDone . Set ( ) ;
throw ;
}
}
/// <summary>
/// Closes the video stream of a video stream is open.
/// </summary>
public void CloseVideoStream ( ) {
lock ( SyncRoot ) {
if ( this . IsBusy = = false ) {
return ;
}
}
if ( _videoTokenSource . IsCancellationRequested = = false ) {
_videoTokenSource . Cancel ( ) ;
_videoStreamTask . Wait ( ) ;
}
_videoTokenSource = new CancellationTokenSource ( ) ;
}
private static async Task VideoWorkerDoWork ( CameraVideoSettings settings , Action < Byte [ ] > onDataCallback , Action onExitCallback ) {
try {
await ProcessRunner . RunProcessAsync (
settings . CommandName ,
settings . CreateProcessArguments ( ) ,
( data , proc ) = > onDataCallback ? . Invoke ( data ) ,
null ,
true ,
_videoTokenSource . Token ) ;
onExitCallback ? . Invoke ( ) ;
} catch {
// swallow
} finally {
Instance . CloseVideoStream ( ) ;
OperationDone . Set ( ) ;
}
}
#endregion
}
2019-02-17 14:08:57 +01:00
}