Spelunx Cavern SDK
 
Loading...
Searching...
No Matches
FrameDataProvider.cs
Go to the documentation of this file.
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4using System.Runtime.InteropServices;
5using Microsoft.Azure.Kinect.Sensor;
6using Microsoft.Azure.Kinect.BodyTracking;
7
8namespace Spelunx.Orbbec {
9 /// Processes data from the ORBBEC sensor in a background thread to produce FrameData.
10 public class FrameDataProvider : IDisposable {
11 public delegate void FinishCallback();
12
13 /// Flag to determine of the background thread has started.
14 public bool HasStarted { get; protected set; } = false;
15 public bool HasData { get; private set; } = false;
16 public string DeviceSerial { get; private set; }
17 public SensorOrientation Orientation { get; private set; }
18
19 // Internal variables.
20 private FrameData frontBuffer = new FrameData();
21 private FrameData backBuffer = new FrameData();
22 private object dataMutex = new object();
23 private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
24 private Task backgroundThread = null;
25
26 public FrameDataProvider(int deviceId, SensorOrientation orientation, FinishCallback onFinish) {
27#if UNITY_EDITOR
28 UnityEditor.EditorApplication.quitting += OnEditorClose;
29#endif
30
31 Orientation = orientation;
32
33 backgroundThread = Task.Run(() => RunBackgroundThreadAsync(deviceId, cancellationTokenSource.Token, onFinish));
34 }
35
36 public void Dispose() {
37#if UNITY_EDITOR
38 UnityEditor.EditorApplication.quitting -= OnEditorClose;
39#endif
40
41 cancellationTokenSource?.Cancel();
42 cancellationTokenSource?.Dispose();
43 cancellationTokenSource = null;
44
45 backgroundThread?.Wait();
46 }
47
48 public bool GetData(ref FrameData output) {
49 lock (dataMutex) {
50 if (!HasData) { return false; }
51
52 var temp = frontBuffer;
53 frontBuffer = output;
54 output = temp;
55 HasData = false;
56
57 return true;
58 }
59 }
60
61 protected void RunBackgroundThreadAsync(int deviceId, CancellationToken token, FinishCallback onFinish) {
62 try {
63 UnityEngine.Debug.Log("Starting body tracker background thread.");
64
65 // Check if this device ID is valid.
66 if (Device.GetInstalledCount() <= deviceId) {
67 throw new Exception("SkeletalFrameDataProvider - Cannot open device ID " + deviceId + ". Only " + Device.GetInstalledCount() + " devices are connected. Terminating thread.");
68 }
69
70 // Open device. The keyword "using" ensures that an IDisposable is properly disposed of even if an exception occurs within the block.
71 using (Device device = Device.Open(deviceId)) { // TODO: Play around with ID
72 DeviceSerial = device.SerialNum; // Assign device serial.
73
74 // Start Sensor Cameras.
75 device.StartCameras(new DeviceConfiguration() {
76 CameraFPS = FPS.FPS30,
77 ColorResolution = ColorResolution.Off,
78 DepthMode = DepthMode.NFOV_Unbinned,
79 WiredSyncMode = WiredSyncMode.Standalone,
80 DisableStreamingIndicator = false // Ensure that the LED light on the sensor is on so that we can visually see what we are connected to.
81 });
82
83 UnityEngine.Debug.Log("SkeletalFrameDataProvider - Open K4A device successfully. Device ID: " + deviceId + ", Serial Number: " + device.SerialNum);
84
85 // Get tracker calibration and configuration.
86 var trackerCalibration = device.GetCalibration();
87 TrackerConfiguration trackerConfig = new TrackerConfiguration() {
88 ProcessingMode = TrackerProcessingMode.Cpu, // Use CPU so we don't have to download the CUDA binaries. This also means that we don't need a NVIDIA GPU.
89 /* SensorOrientation doesn't orientate the skeleton the right way up.
90 * That is implemented seperately in BodyTracker.
91 * This helps the sensor to determine it's orientation so that the data produced is smoother.
92 * For example if the sensor was set to Default and flipped 180 degrees, then the data will be jitery.
93 * Once the orientation is correct, the data is much smoother. */
94 // https://github.com/microsoft/Azure-Kinect-Sensor-SDK/issues/1039
95 SensorOrientation = Orientation
96 };
97
98 bool isFirstFrame = true;
99 TimeSpan initialTimestamp = new TimeSpan(0);
100 using (Tracker tracker = Tracker.Create(trackerCalibration, trackerConfig)) {
101 while (!token.IsCancellationRequested) { // Run until the thread is closed.
102 // Queue latest capture from the device, so that the tracker can process the information.
103 using (Capture sensorCapture = device.GetCapture()) {
104 tracker.EnqueueCapture(sensorCapture);
105 }
106
107 // Now that the tracker has processed the information, try popping the result.
108 using (Frame frame = tracker.PopResult(TimeSpan.Zero, throwOnTimeout: false)) {
109 if (frame == null) {
110 UnityEngine.Debug.Log($"SkeletalFrameDataProvider - ID: {deviceId}, Pop result from tracker timeout!");
111 continue;
112 }
113
114 // Flag that the thread has started.
115 HasStarted = true;
116
117 // Copy bodies.
118 backBuffer.NumDetectedBodies = frame.NumberOfBodies;
119 for (uint i = 0; i < backBuffer.NumDetectedBodies; i++) {
120 backBuffer.Bodies[i].CopyFromBodyTrackingSdk(frame.GetBody(i), trackerCalibration);
121 }
122
123 // Store depth image.
124 Capture bodyFrameCapture = frame.Capture;
125 Image depthImage = bodyFrameCapture.Depth;
126 if (isFirstFrame) {
127 isFirstFrame = false;
128 initialTimestamp = depthImage.DeviceTimestamp;
129 }
130 backBuffer.TimestampInMs = (float)(depthImage.DeviceTimestamp - initialTimestamp).TotalMilliseconds;
131 backBuffer.DepthImageWidth = depthImage.WidthPixels;
132 backBuffer.DepthImageHeight = depthImage.HeightPixels;
133
134 // Read image data from the SDK.
135 var depthFrame = MemoryMarshal.Cast<byte, ushort>(depthImage.Memory.Span);
136
137 // Repack data and store image data.
138 const float MAX_DISPLAYED_DEPTH_IN_MILLIMETERS = 5000.0f;
139 int byteCounter = 0;
140 backBuffer.DepthImageSize = backBuffer.DepthImageWidth * backBuffer.DepthImageHeight * 3;
141 for (int it = backBuffer.DepthImageWidth * backBuffer.DepthImageHeight - 1; it > 0; it--) {
142 byte b = (byte)(depthFrame[it] / MAX_DISPLAYED_DEPTH_IN_MILLIMETERS * 255);
143 backBuffer.DepthImage[byteCounter++] = b;
144 backBuffer.DepthImage[byteCounter++] = b;
145 backBuffer.DepthImage[byteCounter++] = b;
146 }
147
148 // Update data variable that is being read in the UI thread.
149 SwapBuffers();
150 }
151 }
152 tracker.Dispose();
153 }
154 device.Dispose();
155 }
156 } catch (Exception e) {
157 UnityEngine.Debug.Log($"SkeletalFrameDataProvider - ID: {deviceId}, Catching exception for background thread: {e.Message}");
158 } finally {
159 UnityEngine.Debug.Log($"SkeletalFrameDataProvider - ID: {deviceId}, Shutting down background thread.");
160 onFinish?.Invoke();
161 }
162 }
163
164 private void OnEditorClose() { Dispose(); }
165
166 private void SwapBuffers() {
167 lock (dataMutex) {
168 var temp = backBuffer;
169 backBuffer = frontBuffer;
170 frontBuffer = temp;
171 HasData = true;
172 }
173 }
174 }
175}
Processes data from the ORBBEC sensor in a background thread to produce FrameData.
bool GetData(ref FrameData output)
bool HasStarted
Flag to determine of the background thread has started.
void RunBackgroundThreadAsync(int deviceId, CancellationToken token, FinishCallback onFinish)
FrameDataProvider(int deviceId, SensorOrientation orientation, FinishCallback onFinish)
BodyData[] Bodies
Array of bodies. Use NumDetectedBodies to determine how many bodies contain useful data.
Definition: FrameData.cs:21
void CopyFromBodyTrackingSdk(Microsoft.Azure.Kinect.BodyTracking.Body body, Calibration sensorCalibration)
Definition: BodyData.cs:47