blob: 3f682a262d1e6d5a76cb52536f45f05949110fd8 [file] [log] [blame]
Brian Silverman1a675112016-02-20 20:42:49 -05001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2016. All Rights Reserved. */
3/* Open Source Software - may be modified and shared by FRC teams. The code */
4/* must be accompanied by the FIRST BSD license file in the root directory of */
5/* the project. */
6/*----------------------------------------------------------------------------*/
7
Brian Silverman26e4e522015-12-17 01:56:40 -05008#include "USBCamera.h"
9
10#include "Utility.h"
11
12#include <regex>
13#include <chrono>
14#include <thread>
15#include <memory>
16#include <iostream>
17#include <iomanip>
18
19// This macro expands the given imaq function to ensure that it is called and
20// properly checked for an error, calling the wpi_setImaqErrorWithContext
21// macro
22// To call it, just give the name of the function and the arguments
23#define SAFE_IMAQ_CALL(funName, ...) \
24 { \
25 unsigned int error = funName(__VA_ARGS__); \
26 if (error != IMAQdxErrorSuccess) \
27 wpi_setImaqErrorWithContext(error, #funName); \
28 }
29
30/**
31 * Helper function to determine the size of a jpeg. The general structure of
32 * how to parse a jpeg for length can be found in this stackoverflow article:
33 * http://stackoverflow.com/a/1602428. Be sure to also read the comments for
34 * the SOS flag explanation.
35 */
36unsigned int USBCamera::GetJpegSize(void* buffer, unsigned int buffSize) {
37 uint8_t* data = (uint8_t*)buffer;
38 if (!wpi_assert(data[0] == 0xff && data[1] == 0xd8)) return 0;
39 unsigned int pos = 2;
40 while (pos < buffSize) {
41 // All control markers start with 0xff, so if this isn't present,
42 // the JPEG is not valid
43 if (!wpi_assert(data[pos] == 0xff)) return 0;
44 unsigned char t = data[pos + 1];
45 // These are RST markers. We just skip them and move onto the next marker
46 if (t == 0x01 || (t >= 0xd0 && t <= 0xd7)) {
47 pos += 2;
48 } else if (t == 0xd9) {
49 // End of Image, add 2 for this and 0-indexed
50 return pos + 2;
51 } else if (!wpi_assert(t != 0xd8)) {
52 // Another start of image, invalid image
53 return 0;
54 } else if (t == 0xda) {
55 // SOS marker. The next two bytes are a 16-bit big-endian int that is
56 // the length of the SOS header, skip that
57 unsigned int len = (((unsigned int)(data[pos + 2] & 0xff)) << 8 |
58 ((unsigned int)data[pos + 3] & 0xff));
59 pos += len + 2;
60 // The next marker is the first marker that is 0xff followed by a non-RST
61 // element. 0xff followed by 0x00 is an escaped 0xff. 0xd0-0xd7 are RST
62 // markers
63 while (data[pos] != 0xff || data[pos + 1] == 0x00 ||
64 (data[pos + 1] >= 0xd0 && data[pos + 1] <= 0xd7)) {
65 pos += 1;
66 if (pos >= buffSize) return 0;
67 }
68 } else {
69 // This is one of several possible markers. The next two bytes are a
70 // 16-bit
71 // big-endian int with the length of the marker header, skip that then
72 // continue searching
73 unsigned int len = (((unsigned int)(data[pos + 2] & 0xff)) << 8 |
74 ((unsigned int)data[pos + 3] & 0xff));
75 pos += len + 2;
76 }
77 }
78
79 return 0;
80}
81
82USBCamera::USBCamera(std::string name, bool useJpeg)
83 : m_name(name),
84 m_useJpeg(useJpeg) {}
85
86void USBCamera::OpenCamera() {
87 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
88 for (unsigned int i = 0; i < 3; i++) {
89 uInt32 id = 0;
90 // Can't use SAFE_IMAQ_CALL here because we only error on the third time
91 IMAQdxError error = IMAQdxOpenCamera(
92 m_name.c_str(), IMAQdxCameraControlModeController, &id);
93 if (error != IMAQdxErrorSuccess) {
94 // Only error on the 3rd try
95 if (i >= 2) wpi_setImaqErrorWithContext(error, "IMAQdxOpenCamera");
96 // Sleep for a few seconds to ensure the error has been dealt with
97 std::this_thread::sleep_for(std::chrono::milliseconds(2000));
98 } else {
99 m_id = id;
100 m_open = true;
101 return;
102 }
103 }
104}
105
106void USBCamera::CloseCamera() {
107 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
108 if (!m_open) return;
109 SAFE_IMAQ_CALL(IMAQdxCloseCamera, m_id);
110 m_id = 0;
111 m_open = false;
112}
113
114void USBCamera::StartCapture() {
115 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
116 if (!m_open || m_active) return;
117 SAFE_IMAQ_CALL(IMAQdxConfigureGrab, m_id);
118 SAFE_IMAQ_CALL(IMAQdxStartAcquisition, m_id);
119 m_active = true;
120}
121
122void USBCamera::StopCapture() {
123 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
124 if (!m_open || !m_active) return;
125 SAFE_IMAQ_CALL(IMAQdxStopAcquisition, m_id);
126 SAFE_IMAQ_CALL(IMAQdxUnconfigureAcquisition, m_id);
127 m_active = false;
128}
129
130void USBCamera::UpdateSettings() {
131 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
132 bool wasActive = m_active;
133
134 if (wasActive) StopCapture();
135 if (m_open) CloseCamera();
136 OpenCamera();
137
138 uInt32 count = 0;
139 uInt32 currentMode = 0;
140 SAFE_IMAQ_CALL(IMAQdxEnumerateVideoModes, m_id, nullptr, &count, &currentMode);
141 auto modes = std::make_unique<IMAQdxVideoMode[]>(count);
142 SAFE_IMAQ_CALL(IMAQdxEnumerateVideoModes, m_id, modes.get(), &count, &currentMode);
143
144 // Groups are:
145 // 0 - width
146 // 1 - height
147 // 2 - format
148 // 3 - fps
149 std::regex reMode("([0-9]+)\\s*x\\s*([0-9]+)\\s+(.*?)\\s+([0-9.]+)\\s*fps");
150 IMAQdxVideoMode* foundMode = nullptr;
151 IMAQdxVideoMode* currentModePtr = &modes[currentMode];
152 double foundFps = 1000.0;
153
154 // Loop through the modes, and find the match with the lowest fps
155 for (unsigned int i = 0; i < count; i++) {
156 std::cmatch m;
157 if (!std::regex_match(modes[i].Name, m, reMode)) continue;
158 unsigned int width = (unsigned int)std::stoul(m[1].str());
159 unsigned int height = (unsigned int)std::stoul(m[2].str());
160 if (width != m_width) continue;
161 if (height != m_height) continue;
162 double fps = atof(m[4].str().c_str());
163 if (fps < m_fps) continue;
164 if (fps > foundFps) continue;
165 bool isJpeg =
166 m[3].str().compare("jpeg") == 0 || m[3].str().compare("JPEG") == 0;
167 if ((m_useJpeg && !isJpeg) || (!m_useJpeg && isJpeg)) continue;
168 foundMode = &modes[i];
169 foundFps = fps;
170 }
171 if (foundMode != nullptr) {
172 if (foundMode->Value != currentModePtr->Value) {
173 SAFE_IMAQ_CALL(IMAQdxSetAttribute, m_id, IMAQdxAttributeVideoMode,
174 IMAQdxValueTypeU32, foundMode->Value);
175 }
176 }
177
178 if (m_whiteBalance.compare(AUTO) == 0) {
179 SAFE_IMAQ_CALL(IMAQdxSetAttribute, m_id, ATTR_WB_MODE,
180 IMAQdxValueTypeString, AUTO);
181 } else {
182 SAFE_IMAQ_CALL(IMAQdxSetAttribute, m_id, ATTR_WB_MODE,
183 IMAQdxValueTypeString, MANUAL);
184 if (m_whiteBalanceValuePresent)
185 SAFE_IMAQ_CALL(IMAQdxSetAttribute, m_id, ATTR_WB_VALUE,
186 IMAQdxValueTypeU32, m_whiteBalanceValue);
187 }
188
189 if (m_exposure.compare(AUTO) == 0) {
190 SAFE_IMAQ_CALL(IMAQdxSetAttribute, m_id, ATTR_EX_MODE,
191 IMAQdxValueTypeString,
192 std::string("AutoAperaturePriority").c_str());
193 } else {
194 SAFE_IMAQ_CALL(IMAQdxSetAttribute, m_id, ATTR_EX_MODE,
195 IMAQdxValueTypeString, MANUAL);
196 if (m_exposureValuePresent) {
197 double minv = 0.0;
198 double maxv = 0.0;
199 SAFE_IMAQ_CALL(IMAQdxGetAttributeMinimum, m_id, ATTR_EX_VALUE,
200 IMAQdxValueTypeF64, &minv);
201 SAFE_IMAQ_CALL(IMAQdxGetAttributeMaximum, m_id, ATTR_EX_VALUE,
202 IMAQdxValueTypeF64, &maxv);
203 double val = minv + ((maxv - minv) * ((double)m_exposureValue / 100.0));
204 SAFE_IMAQ_CALL(IMAQdxSetAttribute, m_id, ATTR_EX_VALUE,
205 IMAQdxValueTypeF64, val);
206 }
207 }
208
209 SAFE_IMAQ_CALL(IMAQdxSetAttribute, m_id, ATTR_BR_MODE, IMAQdxValueTypeString,
210 MANUAL);
211 double minv = 0.0;
212 double maxv = 0.0;
213 SAFE_IMAQ_CALL(IMAQdxGetAttributeMinimum, m_id, ATTR_BR_VALUE,
214 IMAQdxValueTypeF64, &minv);
215 SAFE_IMAQ_CALL(IMAQdxGetAttributeMaximum, m_id, ATTR_BR_VALUE,
216 IMAQdxValueTypeF64, &maxv);
217 double val = minv + ((maxv - minv) * ((double)m_brightness / 100.0));
218 SAFE_IMAQ_CALL(IMAQdxSetAttribute, m_id, ATTR_BR_VALUE, IMAQdxValueTypeF64,
219 val);
220
221 if (wasActive) StartCapture();
222}
223
224void USBCamera::SetFPS(double fps) {
225 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
226 if (m_fps != fps) {
227 m_needSettingsUpdate = true;
228 m_fps = fps;
229 }
230}
231
232void USBCamera::SetSize(unsigned int width, unsigned int height) {
233 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
234 if (m_width != width || m_height != height) {
235 m_needSettingsUpdate = true;
236 m_width = width;
237 m_height = height;
238 }
239}
240
241void USBCamera::SetBrightness(unsigned int brightness) {
242 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
243 if (m_brightness != brightness) {
244 m_needSettingsUpdate = true;
245 m_brightness = brightness;
246 }
247}
248
249unsigned int USBCamera::GetBrightness() {
250 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
251 return m_brightness;
252}
253
254void USBCamera::SetWhiteBalanceAuto() {
255 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
256 m_whiteBalance = AUTO;
257 m_whiteBalanceValue = 0;
258 m_whiteBalanceValuePresent = false;
259 m_needSettingsUpdate = true;
260}
261
262void USBCamera::SetWhiteBalanceHoldCurrent() {
263 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
264 m_whiteBalance = MANUAL;
265 m_whiteBalanceValue = 0;
266 m_whiteBalanceValuePresent = false;
267 m_needSettingsUpdate = true;
268}
269
270void USBCamera::SetWhiteBalanceManual(unsigned int whiteBalance) {
271 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
272 m_whiteBalance = MANUAL;
273 m_whiteBalanceValue = whiteBalance;
274 m_whiteBalanceValuePresent = true;
275 m_needSettingsUpdate = true;
276}
277
278void USBCamera::SetExposureAuto() {
279 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
280 m_exposure = AUTO;
281 m_exposureValue = 0;
282 m_exposureValuePresent = false;
283 m_needSettingsUpdate = true;
284}
285
286void USBCamera::SetExposureHoldCurrent() {
287 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
288 m_exposure = MANUAL;
289 m_exposureValue = 0;
290 m_exposureValuePresent = false;
291 m_needSettingsUpdate = true;
292}
293
294void USBCamera::SetExposureManual(unsigned int level) {
295 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
296 m_exposure = MANUAL;
297 if (level > 100)
298 m_exposureValue = 100;
299 else
300 m_exposureValue = level;
301 m_exposureValuePresent = true;
302 m_needSettingsUpdate = true;
303}
304
305void USBCamera::GetImage(Image* image) {
306 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
307 if (m_needSettingsUpdate || m_useJpeg) {
308 m_needSettingsUpdate = false;
309 m_useJpeg = false;
310 UpdateSettings();
311 }
312 // BufNum is not actually used for anything at our level, since we are
313 // waiting until the next image is ready anyway
314 uInt32 bufNum;
315 SAFE_IMAQ_CALL(IMAQdxGrab, m_id, image, 1, &bufNum);
316}
317
318unsigned int USBCamera::GetImageData(void* buffer, unsigned int bufferSize) {
319 std::lock_guard<priority_recursive_mutex> lock(m_mutex);
320 if (m_needSettingsUpdate || !m_useJpeg) {
321 m_needSettingsUpdate = false;
322 m_useJpeg = true;
323 UpdateSettings();
324 }
325 // BufNum is not actually used for anything at our level
326 uInt32 bufNum;
327 SAFE_IMAQ_CALL(IMAQdxGetImageData, m_id, buffer, bufferSize,
328 IMAQdxBufferNumberModeLast, 0, &bufNum);
329 return GetJpegSize(buffer, bufferSize);
330}