190 lines
6.8 KiB
C#
190 lines
6.8 KiB
C#
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
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the camera module is busy.
|
|
/// </summary>
|
|
/// <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
|
|
}
|
|
} |