using Unosquare.Swan.Abstractions;
using System;
using Unosquare.Swan.Components;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Unosquare.RaspberryIO.Camera {
///
/// The Raspberry Pi's camera controller wrapping raspistill and raspivid programs.
/// This class is a singleton
///
public class CameraController : SingletonBase {
#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 _videoStreamTask;
#endregion
#region Properties
///
/// Gets a value indicating whether the camera module is busy.
///
///
/// true if this instance is busy; otherwise, false.
///
public Boolean IsBusy => OperationDone.IsSet == false;
#endregion
#region Image Capture Methods
///
/// Captures an image asynchronously.
///
/// The settings.
/// The ct.
/// The image bytes
/// Cannot use camera module because it is currently busy.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "")]
public async Task 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();
}
}
///
/// Captures an image.
///
/// The settings.
/// The image bytes
public Byte[] CaptureImage(CameraStillSettings settings) => this.CaptureImageAsync(settings).GetAwaiter().GetResult();
///
/// Captures a JPEG encoded image asynchronously at 90% quality.
///
/// The width.
/// The height.
/// The ct.
/// The image bytes
public Task 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);
}
///
/// Captures a JPEG encoded image at 90% quality.
///
/// The width.
/// The height.
/// The image bytes
public Byte[] CaptureImageJpeg(Int32 width, Int32 height) => this.CaptureImageJpegAsync(width, height).GetAwaiter().GetResult();
#endregion
#region Video Capture Methods
///
/// Opens the video stream with a timeout of 0 (running indefinitely) at 1080p resolution, variable bitrate and 25 FPS.
/// No preview is shown
///
/// The on data callback.
/// The on exit callback.
public void OpenVideoStream(Action onDataCallback, Action onExitCallback = null) {
CameraVideoSettings settings = new CameraVideoSettings {
CaptureTimeoutMilliseconds = 0,
CaptureDisplayPreview = false,
CaptureWidth = 1920,
CaptureHeight = 1080
};
this.OpenVideoStream(settings, onDataCallback, onExitCallback);
}
///
/// Opens the video stream with the supplied settings. Capture Timeout Milliseconds has to be 0 or greater
///
/// The settings.
/// The on data callback.
/// The on exit callback.
/// Cannot use camera module because it is currently busy.
/// CaptureTimeoutMilliseconds
public void OpenVideoStream(CameraVideoSettings settings, Action 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;
}
}
///
/// Closes the video stream of a video stream is open.
///
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 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
}
}