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