blob: 990c68ab5916f11e7dde6ad61c266d19c021fa10 [file] [log] [blame]
Brian Silverman26e4e522015-12-17 01:56:40 -05001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2014. 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 $(WIND_BASE)/WPILib. */
5/*----------------------------------------------------------------------------*/
6
7#include "Vision/AxisCamera.h"
8
9#include "WPIErrors.h"
10
11#include <cstring>
12#include <sys/types.h>
13#include <sys/socket.h>
14#include <netinet/in.h>
15#include <unistd.h>
16#include <netdb.h>
17#include <Timer.h>
18#include <iostream>
19#include <sstream>
20
21static const unsigned int kMaxPacketSize = 1536;
22static const unsigned int kImageBufferAllocationIncrement = 1000;
23
24static const std::string kWhiteBalanceStrings[] = {
25 "auto", "hold", "fixed_outdoor1", "fixed_outdoor2",
26 "fixed_indoor", "fixed_fluor1", "fixed_fluor2",
27};
28
29static const std::string kExposureControlStrings[] = {
30 "auto", "hold", "flickerfree50", "flickerfree60",
31};
32
33static const std::string kResolutionStrings[] = {
34 "640x480", "480x360", "320x240", "240x180", "176x144", "160x120",
35};
36
37static const std::string kRotationStrings[] = {
38 "0", "180",
39};
40
41/**
42 * AxisCamera constructor
43 * @param cameraHost The host to find the camera at, typically an IP address
44 */
45AxisCamera::AxisCamera(std::string const &cameraHost)
46 : m_cameraHost(cameraHost) {
47 m_captureThread = std::thread(&AxisCamera::Capture, this);
48}
49
50AxisCamera::~AxisCamera() {
51 m_done = true;
52 m_captureThread.join();
53}
54
55/*
56 * Return true if the latest image from the camera has not been retrieved by
57 * calling GetImage() yet.
58 * @return true if the image has not been retrieved yet.
59 */
60bool AxisCamera::IsFreshImage() const { return m_freshImage; }
61
62/**
63 * Get an image from the camera and store it in the provided image.
64 * @param image The imaq image to store the result in. This must be an HSL or
65 * RGB image.
66 * @return 1 upon success, zero on a failure
67 */
68int AxisCamera::GetImage(Image *image) {
69 if (m_imageData.size() == 0) {
70 return 0;
71 }
72
73 std::lock_guard<priority_mutex> lock(m_imageDataMutex);
74
75 Priv_ReadJPEGString_C(image, m_imageData.data(), m_imageData.size());
76
77 m_freshImage = false;
78
79 return 1;
80}
81
82/**
83 * Get an image from the camera and store it in the provided image.
84 * @param image The image to store the result in. This must be an HSL or RGB
85 * image
86 * @return 1 upon success, zero on a failure
87 */
88int AxisCamera::GetImage(ColorImage *image) {
89 return GetImage(image->GetImaqImage());
90}
91
92/**
93 * Instantiate a new image object and fill it with the latest image from the
94 * camera.
95 *
96 * The returned pointer is owned by the caller and is their responsibility to
97 * delete.
98 * @return a pointer to an HSLImage object
99 */
100HSLImage *AxisCamera::GetImage() {
101 auto image = new HSLImage();
102 GetImage(image);
103 return image;
104}
105
106/**
107 * Copy an image into an existing buffer.
108 * This copies an image into an existing buffer rather than creating a new image
109 * in memory. That way a new image is only allocated when the image being copied
110 * is
111 * larger than the destination.
112 * This method is called by the PCVideoServer class.
113 * @param imageData The destination image.
114 * @param numBytes The size of the destination image.
115 * @return 0 if failed (no source image or no memory), 1 if success.
116 */
117int AxisCamera::CopyJPEG(char **destImage, unsigned int &destImageSize,
118 unsigned int &destImageBufferSize) {
119 std::lock_guard<priority_mutex> lock(m_imageDataMutex);
120 if (destImage == nullptr) {
121 wpi_setWPIErrorWithContext(NullParameter, "destImage must not be nullptr");
122 return 0;
123 }
124
125 if (m_imageData.size() == 0) return 0; // if no source image
126
127 if (destImageBufferSize <
128 m_imageData.size()) // if current destination buffer too small
129 {
130 if (*destImage != nullptr) delete[] * destImage;
131 destImageBufferSize = m_imageData.size() + kImageBufferAllocationIncrement;
132 *destImage = new char[destImageBufferSize];
133 if (*destImage == nullptr) return 0;
134 }
135 // copy this image into destination buffer
136 if (*destImage == nullptr) {
137 wpi_setWPIErrorWithContext(NullParameter, "*destImage must not be nullptr");
138 }
139
140 std::copy(m_imageData.begin(), m_imageData.end(), *destImage);
141 destImageSize = m_imageData.size();
142 ;
143 return 1;
144}
145
146/**
147 * Request a change in the brightness of the camera images.
148 * @param brightness valid values 0 .. 100
149 */
150void AxisCamera::WriteBrightness(int brightness) {
151 if (brightness < 0 || brightness > 100) {
152 wpi_setWPIErrorWithContext(ParameterOutOfRange,
153 "Brightness must be from 0 to 100");
154 return;
155 }
156
157 std::lock_guard<priority_mutex> lock(m_parametersMutex);
158
159 if (m_brightness != brightness) {
160 m_brightness = brightness;
161 m_parametersDirty = true;
162 }
163}
164
165/**
166 * @return The configured brightness of the camera images
167 */
168int AxisCamera::GetBrightness() {
169 std::lock_guard<priority_mutex> lock(m_parametersMutex);
170 return m_brightness;
171}
172
173/**
174 * Request a change in the white balance on the camera.
175 * @param whiteBalance Valid values from the <code>WhiteBalance</code> enum.
176 */
177void AxisCamera::WriteWhiteBalance(AxisCamera::WhiteBalance whiteBalance) {
178 std::lock_guard<priority_mutex> lock(m_parametersMutex);
179
180 if (m_whiteBalance != whiteBalance) {
181 m_whiteBalance = whiteBalance;
182 m_parametersDirty = true;
183 }
184}
185
186/**
187 * @return The configured white balances of the camera images
188 */
189AxisCamera::WhiteBalance AxisCamera::GetWhiteBalance() {
190 std::lock_guard<priority_mutex> lock(m_parametersMutex);
191 return m_whiteBalance;
192}
193
194/**
195 * Request a change in the color level of the camera images.
196 * @param colorLevel valid values are 0 .. 100
197 */
198void AxisCamera::WriteColorLevel(int colorLevel) {
199 if (colorLevel < 0 || colorLevel > 100) {
200 wpi_setWPIErrorWithContext(ParameterOutOfRange,
201 "Color level must be from 0 to 100");
202 return;
203 }
204
205 std::lock_guard<priority_mutex> lock(m_parametersMutex);
206
207 if (m_colorLevel != colorLevel) {
208 m_colorLevel = colorLevel;
209 m_parametersDirty = true;
210 }
211}
212
213/**
214 * @return The configured color level of the camera images
215 */
216int AxisCamera::GetColorLevel() {
217 std::lock_guard<priority_mutex> lock(m_parametersMutex);
218 return m_colorLevel;
219}
220
221/**
222 * Request a change in the camera's exposure mode.
223 * @param exposureControl A mode to write in the <code>Exposure</code> enum.
224 */
225void AxisCamera::WriteExposureControl(
226 AxisCamera::ExposureControl exposureControl) {
227 std::lock_guard<priority_mutex> lock(m_parametersMutex);
228
229 if (m_exposureControl != exposureControl) {
230 m_exposureControl = exposureControl;
231 m_parametersDirty = true;
232 }
233}
234
235/**
236 * @return The configured exposure control mode of the camera
237 */
238AxisCamera::ExposureControl AxisCamera::GetExposureControl() {
239 std::lock_guard<priority_mutex> lock(m_parametersMutex);
240 return m_exposureControl;
241}
242
243/**
244 * Request a change in the exposure priority of the camera.
245 * @param exposurePriority Valid values are 0, 50, 100.
246 * 0 = Prioritize image quality
247 * 50 = None
248 * 100 = Prioritize frame rate
249 */
250void AxisCamera::WriteExposurePriority(int exposurePriority) {
251 if (exposurePriority != 0 && exposurePriority != 50 &&
252 exposurePriority != 100) {
253 wpi_setWPIErrorWithContext(ParameterOutOfRange,
254 "Exposure priority must be from 0, 50, or 100");
255 return;
256 }
257
258 std::lock_guard<priority_mutex> lock(m_parametersMutex);
259
260 if (m_exposurePriority != exposurePriority) {
261 m_exposurePriority = exposurePriority;
262 m_parametersDirty = true;
263 }
264}
265
266/**
267 * @return The configured exposure priority of the camera
268 */
269int AxisCamera::GetExposurePriority() {
270 std::lock_guard<priority_mutex> lock(m_parametersMutex);
271 return m_exposurePriority;
272}
273
274/**
275 * Write the maximum frames per second that the camera should send
276 * Write 0 to send as many as possible.
277 * @param maxFPS The number of frames the camera should send in a second,
278 * exposure permitting.
279 */
280void AxisCamera::WriteMaxFPS(int maxFPS) {
281 std::lock_guard<priority_mutex> lock(m_parametersMutex);
282
283 if (m_maxFPS != maxFPS) {
284 m_maxFPS = maxFPS;
285 m_parametersDirty = true;
286 m_streamDirty = true;
287 }
288}
289
290/**
291 * @return The configured maximum FPS of the camera
292 */
293int AxisCamera::GetMaxFPS() {
294 std::lock_guard<priority_mutex> lock(m_parametersMutex);
295 return m_maxFPS;
296}
297
298/**
299 * Write resolution value to camera.
300 * @param resolution The camera resolution value to write to the camera.
301 */
302void AxisCamera::WriteResolution(AxisCamera::Resolution resolution) {
303 std::lock_guard<priority_mutex> lock(m_parametersMutex);
304
305 if (m_resolution != resolution) {
306 m_resolution = resolution;
307 m_parametersDirty = true;
308 m_streamDirty = true;
309 }
310}
311
312/**
313 * @return The configured resolution of the camera (not necessarily the same
314 * resolution as the most recent image, if it was changed recently.)
315 */
316AxisCamera::Resolution AxisCamera::GetResolution() {
317 std::lock_guard<priority_mutex> lock(m_parametersMutex);
318 return m_resolution;
319}
320
321/**
322 * Write the rotation value to the camera.
323 * If you mount your camera upside down, use this to adjust the image for you.
324 * @param rotation The angle to rotate the camera
325 * (<code>AxisCamera::Rotation::k0</code>
326 * or <code>AxisCamera::Rotation::k180</code>)
327 */
328void AxisCamera::WriteRotation(AxisCamera::Rotation rotation) {
329 std::lock_guard<priority_mutex> lock(m_parametersMutex);
330
331 if (m_rotation != rotation) {
332 m_rotation = rotation;
333 m_parametersDirty = true;
334 m_streamDirty = true;
335 }
336}
337
338/**
339 * @return The configured rotation mode of the camera
340 */
341AxisCamera::Rotation AxisCamera::GetRotation() {
342 std::lock_guard<priority_mutex> lock(m_parametersMutex);
343 return m_rotation;
344}
345
346/**
347 * Write the compression value to the camera.
348 * @param compression Values between 0 and 100.
349 */
350void AxisCamera::WriteCompression(int compression) {
351 if (compression < 0 || compression > 100) {
352 wpi_setWPIErrorWithContext(ParameterOutOfRange,
353 "Compression must be from 0 to 100");
354 return;
355 }
356
357 std::lock_guard<priority_mutex> lock(m_parametersMutex);
358
359 if (m_compression != compression) {
360 m_compression = compression;
361 m_parametersDirty = true;
362 m_streamDirty = true;
363 }
364}
365
366/**
367 * @return The configured compression level of the camera
368 */
369int AxisCamera::GetCompression() {
370 std::lock_guard<priority_mutex> lock(m_parametersMutex);
371 return m_compression;
372}
373
374/**
375 * Method called in the capture thread to receive images from the camera
376 */
377void AxisCamera::Capture() {
378 int consecutiveErrors = 0;
379
380 // Loop on trying to setup the camera connection. This happens in a background
381 // thread so it shouldn't effect the operation of user programs.
382 while (!m_done) {
383 std::string requestString =
384 "GET /mjpg/video.mjpg HTTP/1.1\n"
385 "User-Agent: HTTPStreamClient\n"
386 "Connection: Keep-Alive\n"
387 "Cache-Control: no-cache\n"
388 "Authorization: Basic RlJDOkZSQw==\n\n";
389 m_captureMutex.lock();
390 m_cameraSocket = CreateCameraSocket(requestString, consecutiveErrors > 5);
391 if (m_cameraSocket != -1) {
392 ReadImagesFromCamera();
393 consecutiveErrors = 0;
394 } else {
395 consecutiveErrors++;
396 }
397 m_captureMutex.unlock();
398 Wait(0.5);
399 }
400}
401
402/**
403 * This function actually reads the images from the camera.
404 */
405void AxisCamera::ReadImagesFromCamera() {
406 char *imgBuffer = nullptr;
407 int imgBufferLength = 0;
408
409 // TODO: these recv calls must be non-blocking. Otherwise if the camera
410 // fails during a read, the code hangs and never retries when the camera comes
411 // back up.
412
413 int counter = 2;
414 while (!m_done) {
415 char initialReadBuffer[kMaxPacketSize] = "";
416 char intermediateBuffer[1];
417 char *trailingPtr = initialReadBuffer;
418 int trailingCounter = 0;
419 while (counter) {
420 // TODO: fix me... this cannot be the most efficient way to approach this,
421 // reading one byte at a time.
422 if (recv(m_cameraSocket, intermediateBuffer, 1, 0) == -1) {
423 wpi_setErrnoErrorWithContext("Failed to read image header");
424 close(m_cameraSocket);
425 return;
426 }
427 strncat(initialReadBuffer, intermediateBuffer, 1);
428 // trailingCounter ensures that we start looking for the 4 byte string
429 // after
430 // there is at least 4 bytes total. Kind of obscure.
431 // look for 2 blank lines (\r\n)
432 if (nullptr != strstr(trailingPtr, "\r\n\r\n")) {
433 --counter;
434 }
435 if (++trailingCounter >= 4) {
436 trailingPtr++;
437 }
438 }
439 counter = 1;
440 char *contentLength = strstr(initialReadBuffer, "Content-Length: ");
441 if (contentLength == nullptr) {
442 wpi_setWPIErrorWithContext(IncompatibleMode,
443 "No content-length token found in packet");
444 close(m_cameraSocket);
445 if (imgBuffer) delete[] imgBuffer;
446 return;
447 }
448 contentLength = contentLength + 16; // skip past "content length"
449 int readLength = atol(contentLength); // get the image byte count
450
451 // Make sure buffer is large enough
452 if (imgBufferLength < readLength) {
453 if (imgBuffer) delete[] imgBuffer;
454 imgBufferLength = readLength + kImageBufferAllocationIncrement;
455 imgBuffer = new char[imgBufferLength];
456 if (imgBuffer == nullptr) {
457 imgBufferLength = 0;
458 continue;
459 }
460 }
461
462 // Read the image data for "Content-Length" bytes
463 int bytesRead = 0;
464 int remaining = readLength;
465 while (bytesRead < readLength) {
466 int bytesThisRecv =
467 recv(m_cameraSocket, &imgBuffer[bytesRead], remaining, 0);
468 bytesRead += bytesThisRecv;
469 remaining -= bytesThisRecv;
470 }
471
472 // Update image
473 {
474 std::lock_guard<priority_mutex> lock(m_imageDataMutex);
475
476 m_imageData.assign(imgBuffer, imgBuffer + imgBufferLength);
477 m_freshImage = true;
478 }
479
480 if (WriteParameters()) {
481 break;
482 }
483 }
484
485 close(m_cameraSocket);
486}
487
488/**
489 * Send a request to the camera to set all of the parameters. This is called
490 * in the capture thread between each frame. This strategy avoids making lots
491 * of redundant HTTP requests, accounts for failed initial requests, and
492 * avoids blocking calls in the main thread unless necessary.
493 *
494 * This method does nothing if no parameters have been modified since it last
495 * completely successfully.
496 *
497 * @return <code>true</code> if the stream should be restarted due to a
498 * parameter changing.
499 */
500bool AxisCamera::WriteParameters() {
501 if (m_parametersDirty) {
502 std::stringstream request;
503 request << "GET /axis-cgi/admin/param.cgi?action=update";
504
505 m_parametersMutex.lock();
506 request << "&ImageSource.I0.Sensor.Brightness=" << m_brightness;
507 request << "&ImageSource.I0.Sensor.WhiteBalance="
508 << kWhiteBalanceStrings[m_whiteBalance];
509 request << "&ImageSource.I0.Sensor.ColorLevel=" << m_colorLevel;
510 request << "&ImageSource.I0.Sensor.Exposure="
511 << kExposureControlStrings[m_exposureControl];
512 request << "&ImageSource.I0.Sensor.ExposurePriority=" << m_exposurePriority;
513 request << "&Image.I0.Stream.FPS=" << m_maxFPS;
514 request << "&Image.I0.Appearance.Resolution="
515 << kResolutionStrings[m_resolution];
516 request << "&Image.I0.Appearance.Compression=" << m_compression;
517 request << "&Image.I0.Appearance.Rotation=" << kRotationStrings[m_rotation];
518 m_parametersMutex.unlock();
519
520 request << " HTTP/1.1" << std::endl;
521 request << "User-Agent: HTTPStreamClient" << std::endl;
522 request << "Connection: Keep-Alive" << std::endl;
523 request << "Cache-Control: no-cache" << std::endl;
524 request << "Authorization: Basic RlJDOkZSQw==" << std::endl;
525 request << std::endl;
526
527 int socket = CreateCameraSocket(request.str(), false);
528 if (socket == -1) {
529 wpi_setErrnoErrorWithContext("Error setting camera parameters");
530 } else {
531 close(socket);
532 m_parametersDirty = false;
533
534 if (m_streamDirty) {
535 m_streamDirty = false;
536 return true;
537 }
538 }
539 }
540
541 return false;
542}
543
544/**
545 * Create a socket connected to camera
546 * Used to create a connection to the camera for both capturing images and
547 * setting parameters.
548 * @param requestString The initial request string to send upon successful
549 * connection.
550 * @param setError If true, rais an error if there's a problem creating the
551 * connection.
552 * This is only enabled after several unsucessful connections, so a single one
553 * doesn't
554 * cause an error message to be printed if it immediately recovers.
555 * @return -1 if failed, socket handle if successful.
556 */
557int AxisCamera::CreateCameraSocket(std::string const &requestString,
558 bool setError) {
559 struct addrinfo *address = nullptr;
560 int camSocket;
561
562 /* create socket */
563 if ((camSocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
564 if (setError)
565 wpi_setErrnoErrorWithContext("Failed to create the camera socket");
566 return -1;
567 }
568
569 if (getaddrinfo(m_cameraHost.c_str(), "80", nullptr, &address) == -1) {
570 if (setError) {
571 wpi_setErrnoErrorWithContext("Failed to create the camera socket");
572 close(camSocket);
573 }
574 return -1;
575 }
576
577 /* connect to server */
578 if (connect(camSocket, address->ai_addr, address->ai_addrlen) == -1) {
579 if (setError)
580 wpi_setErrnoErrorWithContext("Failed to connect to the camera");
581 freeaddrinfo(address);
582 close(camSocket);
583 return -1;
584 }
585
586 freeaddrinfo(address);
587
588 int sent = send(camSocket, requestString.c_str(), requestString.size(), 0);
589 if (sent == -1) {
590 if (setError)
591 wpi_setErrnoErrorWithContext("Failed to send a request to the camera");
592 close(camSocket);
593 return -1;
594 }
595
596 return camSocket;
597}