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

Commit

Permalink
Annotation refresh for date/time text overlays (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
techyian authored Aug 29, 2020
2 parents 1f33ad0 + e95ca86 commit ed27443
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 8 deletions.
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());
}
}
}
}

0 comments on commit ed27443

Please sign in to comment.