Viewing is open to everyone. Downloading and starring need a GitHub sign-in.
Sign in with GitHub
CKeyScanSession.cs
v0.1.0-build.85
Latest
Commit ede38f5
Sign in with GitHub to download. Each download is recorded with your GitHub username.
| 1 | using System; |
| 2 | using System.Diagnostics; |
| 3 | using System.Threading; |
| 4 | using OpenCKey.Interop; |
| 5 | |
| 6 | namespace OpenCKey |
| 7 | { |
| 8 | /// <summary> |
| 9 | /// A continuous scanning session (for example while a camera view is |
| 10 | /// visible). It decodes frames with <see cref="CKeyDecoder"/> and drives the |
| 11 | /// telemetry session defined in docs/adoption-and-telemetry.md: it starts |
| 12 | /// when the session is created, succeeds at the first decode, and fails when |
| 13 | /// it is disposed (or restarted) without one, or after 30 s without a decode. |
| 14 | /// Frames and their processing times are counted. Nothing about the payload |
| 15 | /// is recorded. With telemetry off (mode 0) it is a plain decoder loop. |
| 16 | /// </summary> |
| 17 | /// <example> |
| 18 | /// <code> |
| 19 | /// using var session = new CKeyScanSession(); |
| 20 | /// foreach (var frame in camera.Frames) |
| 21 | /// { |
| 22 | /// CKeyResult? result = session.ProcessFrame(frame.Gray, frame.Width, frame.Height, frame.Stride); |
| 23 | /// if (result != null) { Show(result.Text, result.Action); break; } |
| 24 | /// } |
| 25 | /// </code> |
| 26 | /// </example> |
| 27 | public sealed class CKeyScanSession : IDisposable |
| 28 | { |
| 29 | private int _decoded; |
| 30 | private int _disposed; |
| 31 | |
| 32 | /// <summary>Starts a session.</summary> |
| 33 | public CKeyScanSession() |
| 34 | { |
| 35 | Begin(); |
| 36 | } |
| 37 | |
| 38 | /// <summary>True once a frame of this session has decoded.</summary> |
| 39 | public bool HasDecoded => Volatile.Read(ref _decoded) != 0; |
| 40 | |
| 41 | /// <summary> |
| 42 | /// Decodes one grayscale frame and counts it. Returns the result, or null |
| 43 | /// when nothing decodes. Later decodes in the same session are still |
| 44 | /// returned but are not counted again. |
| 45 | /// </summary> |
| 46 | public CKeyResult? ProcessFrame(ReadOnlySpan<byte> gray, int width, int height, int stride) |
| 47 | { |
| 48 | if (Volatile.Read(ref _disposed) != 0) throw new ObjectDisposedException(nameof(CKeyScanSession)); |
| 49 | long started = Stopwatch.GetTimestamp(); |
| 50 | CKeyResult? result = CKeyDecoder.Decode(gray, width, height, stride); |
| 51 | long elapsedTicks = Stopwatch.GetTimestamp() - started; |
| 52 | RecordFrame(TimeSpan.FromSeconds(elapsedTicks / (double)Stopwatch.Frequency)); |
| 53 | if (result != null) RecordSuccess(result); |
| 54 | return result; |
| 55 | } |
| 56 | |
| 57 | /// <summary> |
| 58 | /// Counts a frame processed by the host's own pipeline (when it does not |
| 59 | /// call <see cref="ProcessFrame"/>). |
| 60 | /// </summary> |
| 61 | public void RecordFrame(TimeSpan processingTime) |
| 62 | { |
| 63 | if (!CKeyTelemetry.IsAvailable) return; |
| 64 | double ms = Math.Round(Math.Max(0, processingTime.TotalMilliseconds)); |
| 65 | uint processing = ms >= uint.MaxValue ? uint.MaxValue : (uint)ms; |
| 66 | NativeTelemetry.ckey_telemetry_frame(MonotonicClock.NowMs, processing); |
| 67 | } |
| 68 | |
| 69 | /// <summary>Records the session's first valid decode (later calls are ignored).</summary> |
| 70 | public void RecordSuccess(CKeyResult result) |
| 71 | { |
| 72 | if (result == null) throw new ArgumentNullException(nameof(result)); |
| 73 | if (Interlocked.Exchange(ref _decoded, 1) != 0) return; |
| 74 | if (!CKeyTelemetry.IsAvailable) return; |
| 75 | NativeTelemetry.ckey_telemetry_session_success(MonotonicClock.NowMs, result.TelemetryProfileCode); |
| 76 | } |
| 77 | |
| 78 | /// <summary> |
| 79 | /// "Scan again": ends the current session (failed if it had no decode) |
| 80 | /// and starts a new one. |
| 81 | /// </summary> |
| 82 | public void Restart() |
| 83 | { |
| 84 | if (Volatile.Read(ref _disposed) != 0) throw new ObjectDisposedException(nameof(CKeyScanSession)); |
| 85 | Interlocked.Exchange(ref _decoded, 0); |
| 86 | Begin(); // the core ends an open session as failed first |
| 87 | } |
| 88 | |
| 89 | /// <summary>Ends the session (failed if it had no decode).</summary> |
| 90 | public void Dispose() |
| 91 | { |
| 92 | if (Interlocked.Exchange(ref _disposed, 1) != 0) return; |
| 93 | if (!CKeyTelemetry.IsAvailable) return; |
| 94 | NativeTelemetry.ckey_telemetry_session_end(MonotonicClock.NowMs); |
| 95 | } |
| 96 | |
| 97 | private static void Begin() |
| 98 | { |
| 99 | if (!CKeyTelemetry.IsAvailable) return; |
| 100 | CKeyTelemetry.EnsureClient(); |
| 101 | NativeTelemetry.ckey_telemetry_session_begin(MonotonicClock.NowMs); |
| 102 | if (NativeTelemetry.ckey_telemetry_get_mode() == CKey.TelemetryOn) TelemetrySender.EnsureStarted(); |
| 103 | } |
| 104 | } |
| 105 | } |