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

annotation refresh for date/time text overlays #167

Merged
merged 7 commits into from
Aug 29, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
65 changes: 61 additions & 4 deletions src/MMALSharp/Config/AnnotateImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,42 @@ public enum JustifyText
Right
}

/// <summary>
/// Used to ensure the date/time annotations are updated for longer-running operations such as video recording or streaming.
/// </summary>
public enum DateTimeTextRefreshRate
{
/// <summary>
/// Do not automatically refresh the <see cref="AnnotateImage.ShowDateText"/> and <see cref="AnnotateImage.ShowTimeText"/> annotations.
/// These annotations can be explicitly refreshed by calling <see cref="MMALCamera.EnableAnnotation"/>.
/// </summary>
Disabled = 0,

/// <summary>
/// Typically used when the time is not displayed.
/// Update interval is once per minute.
/// </summary>
Daily = 60000,

/// <summary>
/// Typically used with the default "HH:mm" <see cref="AnnotateImage.TimeFormat"/>.
/// Update interval is once per second.
/// </summary>
Minutes = 1000,

/// <summary>
/// Useful if the <see cref="AnnotateImage.TimeFormat"/> is altered to display seconds, such as "HH:mm:ss".
/// Update interval is 250ms.
/// </summary>
Seconds = 250,

/// <summary>
/// Useful if the <see cref="AnnotateImage.TimeFormat"/> is altered to display fractional seconds, such as "HH:mm:ss.ffff".
/// Update interval is 41ms, which approximately equates to 24FPS.
/// </summary>
SubSecond = 41
}

/// <summary>
/// The <see cref="AnnotateImage"/> type is for use with the image annotation functionality.
/// This will produce a textual overlay on image stills depending on the options enabled.
Expand All @@ -42,12 +78,12 @@ public class AnnotateImage
/// <summary>
/// The text size to use.
/// </summary>
public int TextSize { get; set; }
public int TextSize { get; set; } = 12;

/// <summary>
/// The <see cref="Color"/> of the text.
/// </summary>
public Color TextColour { get; set; }
public Color TextColour { get; set; } = Color.White;

/// <summary>
/// The <see cref="Color"/> of the background. Note: AllowCustomBackgroundColour should be enabled
Expand Down Expand Up @@ -93,12 +129,28 @@ public class AnnotateImage
/// <summary>
/// Show the current date.
/// </summary>
public bool ShowDateText { get; set; }
public bool ShowDateText { get; set; } = true;

/// <summary>
/// Show the current time.
/// </summary>
public bool ShowTimeText { get; set; }
public bool ShowTimeText { get; set; } = true;

/// <summary>
/// The DateTime format string applied when <see cref="ShowDateText"/> is true. The default is "dd/MM/yyyy".
/// </summary>
public string DateFormat { get; set; } = "dd/MM/yyyy";

/// <summary>
/// The DateTime format string applied when <see cref="ShowTimeText"/> is true. The default is "HH:mm".
/// </summary>
public string TimeFormat { get; set; } = "HH:mm";

/// <summary>
/// The approximate frequency at which date and time annotations are refreshed. The default is
/// <see cref="DateTimeTextRefreshRate.Minutes"/> which matches the resolution of the default <see cref="TimeFormat"/>.
/// </summary>
public DateTimeTextRefreshRate RefreshRate { get; set; } = DateTimeTextRefreshRate.Minutes;

/// <summary>
/// Justify annotation text.
Expand All @@ -115,6 +167,11 @@ public class AnnotateImage
/// </summary>
public int YOffset { get; set; }

/// <summary>
/// Creates a new instance of <see cref="AnnotateImage"/>.
/// </summary>
public AnnotateImage() { }

/// <summary>
/// Creates a new instance of <see cref="AnnotateImage"/>.
/// </summary>
Expand Down
52 changes: 50 additions & 2 deletions src/MMALSharp/MMALCamera.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,16 +363,36 @@ public async Task TakePictureTimelapse(IFileStreamCaptureHandler handler, MMALEn

this.Camera.SetShutterSpeed(MMALCameraConfig.ShutterSpeed);

// Prepare arguments for the annotation-refresh task
var ctsRefreshAnnotation = new CancellationTokenSource();
var refreshInterval = (int)(MMALCameraConfig.Annotate?.RefreshRate ?? 0);

if (!(MMALCameraConfig.Annotate?.ShowDateText ?? false) && !(MMALCameraConfig.Annotate?.ShowTimeText ?? false))
{
refreshInterval = 0;
}

// We now begin capturing on the camera, processing will commence based on the pipeline configured.
this.StartCapture(cameraPort);

if (cancellationToken == CancellationToken.None)
{
await Task.WhenAll(tasks).ConfigureAwait(false);
await Task.WhenAny(
Task.WhenAll(tasks),
RefreshAnnotations(refreshInterval, ctsRefreshAnnotation.Token)
).ConfigureAwait(false);

ctsRefreshAnnotation.Cancel();
}
else
{
await Task.WhenAny(Task.WhenAll(tasks), cancellationToken.AsTask()).ConfigureAwait(false);
await Task.WhenAny(
Task.WhenAll(tasks),
RefreshAnnotations(refreshInterval, ctsRefreshAnnotation.Token),
cancellationToken.AsTask()
).ConfigureAwait(false);

ctsRefreshAnnotation.Cancel();

foreach (var component in handlerComponents)
{
Expand Down Expand Up @@ -504,6 +524,34 @@ public void Cleanup()
BcmHost.bcm_host_deinit();
}

/// <summary>
/// Periodically invokes <see cref="MMALCameraComponentExtensions.SetAnnotateSettings(MMALCameraComponent)"/> to update date/time annotations.
/// </summary>
/// <param name="msInterval">Update frequency in milliseconds, or 0 to disable.</param>
/// <param name="cancellationToken">A CancellationToken to observe while waiting for a task to complete.</param>
/// <returns>The awaitable Task.</returns>
private async Task RefreshAnnotations(int msInterval, CancellationToken cancellationToken)
{
try
{
if (msInterval == 0)
{
await Task.Delay(Timeout.Infinite, cancellationToken).ConfigureAwait(false);
}
else
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(msInterval, cancellationToken).ConfigureAwait(false);
this.Camera.SetAnnotateSettings();
}
}
}
catch (OperationCanceledException)
{ // disregard token cancellation
}
}

/// <summary>
/// Acts as an isolated processor specifically used when capturing raw frames from the camera component.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/MMALSharp/MMALCameraExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,12 @@ internal static void SetAnnotateSettings(this MMALCameraComponent camera)

if (MMALCameraConfig.Annotate.ShowTimeText)
{
sb.Append(DateTime.Now.ToString("HH:mm") + " ");
sb.Append(DateTime.Now.ToString(MMALCameraConfig.Annotate.TimeFormat) + " ");
}

if (MMALCameraConfig.Annotate.ShowDateText)
{
sb.Append(DateTime.Now.ToString("dd/MM/yyyy") + " ");
sb.Append(DateTime.Now.ToString(MMALCameraConfig.Annotate.DateFormat) + " ");
}

if (MMALCameraConfig.Annotate.ShowShutterSettings)
Expand Down
1 change: 1 addition & 0 deletions tests/MMALSharp.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public static void SetConfigurationDefaults()
MMALCameraConfig.Resolution = Resolution.As03MPixel;
MMALCameraConfig.AnalogGain = 0;
MMALCameraConfig.DigitalGain = 0;
MMALCameraConfig.Annotate = null;
}

public static void CleanDirectory(string directory)
Expand Down
37 changes: 37 additions & 0 deletions tests/MMALSharp.Tests/VideoEncoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -536,5 +536,42 @@ public async Task TakeVideoAndStoreTimestamps()
Fixture.CheckAndAssertFilepath($"{vidCaptureHandler.Directory}/{vidCaptureHandler.CurrentFilename}.pts");
}
}

[Fact]
public async Task AnnotateVideoRefreshSeconds()
{
TestHelper.BeginTest("Video - AnnotateVideo");
TestHelper.SetConfigurationDefaults();
TestHelper.CleanDirectory("/home/pi/videos/tests");

MMALCameraConfig.Annotate = new AnnotateImage();
MMALCameraConfig.Annotate.RefreshRate = DateTimeTextRefreshRate.Seconds;
MMALCameraConfig.Annotate.TimeFormat = "HH:mm:ss";

using (var vidCaptureHandler = new VideoStreamCaptureHandler("/home/pi/videos/tests", "h264"))
using (var vidEncoder = new MMALVideoEncoder())
using (var renderer = new MMALVideoRenderer())
{
Fixture.MMALCamera.ConfigureCameraSettings();

var portConfig = new MMALPortConfig(MMALEncoding.H264, MMALEncoding.I420, quality: 10, bitrate: MMALVideoEncoder.MaxBitrateLevel4);

// Create our component pipeline. Here we are using the H.264 standard with a YUV420 pixel format. The video will be taken at 25Mb/s.
vidEncoder.ConfigureOutputPort(portConfig, vidCaptureHandler);

Fixture.MMALCamera.Camera.VideoPort.ConnectTo(vidEncoder);
Fixture.MMALCamera.Camera.PreviewPort.ConnectTo(renderer);

// Camera warm up time
await Task.Delay(2000);

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

// Take video for 30 seconds.
await Fixture.MMALCamera.ProcessAsync(Fixture.MMALCamera.Camera.VideoPort, cts.Token);

Fixture.CheckAndAssertFilepath(vidCaptureHandler.GetFilepath());
}
}
}
}