blob: 14aa4b3095f8e5fa24079d817ab376b1f650b9fc [file] [log] [blame]
jerrymf1579332013-02-07 01:56:28 +00001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2008. 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 <string.h>
10#include "NetworkCommunication/UsageReporting.h"
11#include "Synchronized.h"
12#include "Vision/PCVideoServer.h"
13#include "WPIErrors.h"
14
15/** Private NI function to decode JPEG */
16IMAQ_FUNC int Priv_ReadJPEGString_C(Image* _image, const unsigned char* _string, UINT32 _stringLength);
17
18// Max packet without jumbo frames is 1500... add 36 because??
19#define kMaxPacketSize 1536
20#define kImageBufferAllocationIncrement 1000
21
22AxisCamera *AxisCamera::_instance = NULL;
23
24/**
25 * AxisCamera constructor
26 */
27AxisCamera::AxisCamera(const char *ipAddress)
28 : AxisCameraParams(ipAddress)
29 , m_cameraSocket(ERROR)
30 , m_protectedImageBuffer(NULL)
31 , m_protectedImageBufferLength(0)
32 , m_protectedImageSize(0)
33 , m_protectedImageSem(NULL)
34 , m_freshImage(false)
35 , m_imageStreamTask("cameraTask", (FUNCPTR)s_ImageStreamTaskFunction)
36 , m_videoServer(NULL)
37{
38 m_protectedImageSem = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE | SEM_DELETE_SAFE);
39
40#if JAVA_CAMERA_LIB != 1
41 nUsageReporting::report(nUsageReporting::kResourceType_AxisCamera, ipAddress == NULL ? 1 : 2);
42#endif
43
44 if (!StatusIsFatal())
45 m_imageStreamTask.Start((int)this);
46}
47
48/**
49 * Destructor
50 */
51AxisCamera::~AxisCamera()
52{
53 delete m_videoServer;
54 m_videoServer = NULL;
55
56 m_imageStreamTask.Stop();
57 close(m_cameraSocket);
58
59 SemSet_t::iterator it = m_newImageSemSet.begin();
60 SemSet_t::iterator end = m_newImageSemSet.end();
61 for (;it != end; it++)
62 {
63 semDelete(*it);
64 }
65 m_newImageSemSet.clear();
66
67 semDelete(m_protectedImageSem);
68}
69
70/**
71 * Get a pointer to the AxisCamera object, if the object does not exist, create it
72 * To use the camera on port 2 of a cRIO-FRC, pass "192.168.0.90" to the first GetInstance call.
73 * @return reference to AxisCamera object
74 */
75AxisCamera &AxisCamera::GetInstance(const char *cameraIP)
76{
77 if (NULL == _instance)
78 {
79 _instance = new AxisCamera(cameraIP);
80
81 _instance->m_videoServer = new PCVideoServer();
82 }
83
84 return *_instance;
85}
86
87/**
88 * Called by Java to delete the camera... how thoughtful
89 */
90void AxisCamera::DeleteInstance()
91{
92 delete _instance;
93 _instance = NULL;
94}
95
96/**
97 * Return true if the latest image from the camera has not been retrieved by calling GetImage() yet.
98 * @return true if the image has not been retrieved yet.
99 */
100bool AxisCamera::IsFreshImage()
101{
102 return m_freshImage;
103}
104
105/**
106 * Get the semaphore to be used to synchronize image access with camera acquisition
107 *
108 * Call semTake on the returned semaphore to block until a new image is acquired.
109 *
110 * The semaphore is owned by the AxisCamera class and will be deleted when the class is destroyed.
111 * @return A semaphore to notify when new image is received
112 */
113SEM_ID AxisCamera::GetNewImageSem()
114{
115 SEM_ID sem = semBCreate (SEM_Q_PRIORITY, SEM_EMPTY);
116 m_newImageSemSet.insert(sem);
117 return sem;
118}
119
120/**
121 * Get an image from the camera and store it in the provided image.
122 * @param image The imaq image to store the result in. This must be an HSL or RGB image
123 * This function is called by Java.
124 * @return 1 upon success, zero on a failure
125 */
126int AxisCamera::GetImage(Image* imaqImage)
127{
128 if (m_protectedImageBuffer == NULL)
129 return 0;
130 Synchronized sync(m_protectedImageSem);
131 Priv_ReadJPEGString_C(imaqImage,
132 (unsigned char*)m_protectedImageBuffer, m_protectedImageSize);
133 m_freshImage = false;
134 return 1;
135}
136
137#if JAVA_CAMERA_LIB != 1
138/**
139 * Get an image from the camera and store it in the provided image.
140 * @param image The image to store the result in. This must be an HSL or RGB image
141 * @return 1 upon success, zero on a failure
142 */
143int AxisCamera::GetImage(ColorImage* image)
144{
145 return GetImage(image->GetImaqImage());
146}
147
148/**
149 * Instantiate a new image object and fill it with the latest image from the camera.
150 *
151 * The returned pointer is owned by the caller and is their responsibility to delete.
152 * @return a pointer to an HSLImage object
153 */
154HSLImage* AxisCamera::GetImage()
155{
156 HSLImage *image = new HSLImage();
157 GetImage(image);
158 return image;
159}
160#endif
161
162/**
163 * Copy an image into an existing buffer.
164 * This copies an image into an existing buffer rather than creating a new image
165 * in memory. That way a new image is only allocated when the image being copied is
166 * larger than the destination.
167 * This method is called by the PCVideoServer class.
168 * @param imageData The destination image.
169 * @param numBytes The size of the destination image.
170 * @return 0 if failed (no source image or no memory), 1 if success.
171 */
172int AxisCamera::CopyJPEG(char **destImage, int &destImageSize, int &destImageBufferSize)
173{
174 Synchronized sync(m_protectedImageSem);
175 if (destImage == NULL)
176 wpi_setWPIErrorWithContext(NullParameter, "destImage must not be NULL");
177
178 if (m_protectedImageBuffer == NULL || m_protectedImageSize <= 0)
179 return 0; // if no source image
180
181 if (destImageBufferSize < m_protectedImageSize) // if current destination buffer too small
182 {
183 if (*destImage != NULL) delete [] *destImage;
184 destImageBufferSize = m_protectedImageSize + kImageBufferAllocationIncrement;
185 *destImage = new char[destImageBufferSize];
186 if (*destImage == NULL) return 0;
187 }
188 // copy this image into destination buffer
189 if (*destImage == NULL)
190 {
191 wpi_setWPIErrorWithContext(NullParameter, "*destImage must not be NULL");
192 }
193 // TODO: Is this copy realy necessary... perhaps we can simply transmit while holding the protected buffer
194 memcpy(*destImage, m_protectedImageBuffer, m_protectedImageSize);
195 destImageSize = m_protectedImageSize;
196 return 1;
197}
198
199/**
200 * Static interface that will cause an instantiation if necessary.
201 * This static stub is directly spawned as a task to read images from the camera.
202 */
203int AxisCamera::s_ImageStreamTaskFunction(AxisCamera *thisPtr)
204{
205 return thisPtr->ImageStreamTaskFunction();
206}
207
208/**
209 * Task spawned by AxisCamera constructor to receive images from cam
210 * If setNewImageSem has been called, this function does a semGive on each new image
211 * Images can be accessed by calling getImage()
212 */
213int AxisCamera::ImageStreamTaskFunction()
214{
215 // Loop on trying to setup the camera connection. This happens in a background
216 // thread so it shouldn't effect the operation of user programs.
217 while (1)
218 {
219 const char *requestString = "GET /mjpg/video.mjpg HTTP/1.1\n\
220User-Agent: HTTPStreamClient\n\
221Connection: Keep-Alive\n\
222Cache-Control: no-cache\n\
223Authorization: Basic RlJDOkZSQw==\n\n";
224 semTake(m_socketPossessionSem, WAIT_FOREVER);
225 m_cameraSocket = CreateCameraSocket(requestString);
226 if (m_cameraSocket == ERROR)
227 {
228 // Don't hammer the camera if it isn't ready.
229 semGive(m_socketPossessionSem);
230 taskDelay(1000);
231 }
232 else
233 {
234 ReadImagesFromCamera();
235 }
236 }
237}
238
239/**
240 * This function actually reads the images from the camera.
241 */
242int AxisCamera::ReadImagesFromCamera()
243{
244 char *imgBuffer = NULL;
245 int imgBufferLength = 0;
246 //Infinite loop, task deletion handled by taskDeleteHook
247 // Socket cleanup handled by destructor
248
249 // TODO: these recv calls must be non-blocking. Otherwise if the camera
250 // fails during a read, the code hangs and never retries when the camera comes
251 // back up.
252
253 int counter = 2;
254 while (1)
255 {
256 char initialReadBuffer[kMaxPacketSize] = "";
257 char intermediateBuffer[1];
258 char *trailingPtr = initialReadBuffer;
259 int trailingCounter = 0;
260 while (counter)
261 {
262 // TODO: fix me... this cannot be the most efficient way to approach this, reading one byte at a time.
263 if(recv(m_cameraSocket, intermediateBuffer, 1, 0) == ERROR)
264 {
265 wpi_setErrnoErrorWithContext("Failed to read image header");
266 close (m_cameraSocket);
267 return ERROR;
268 }
269 strncat(initialReadBuffer, intermediateBuffer, 1);
270 // trailingCounter ensures that we start looking for the 4 byte string after
271 // there is at least 4 bytes total. Kind of obscure.
272 // look for 2 blank lines (\r\n)
273 if (NULL != strstr(trailingPtr, "\r\n\r\n"))
274 {
275 --counter;
276 }
277 if (++trailingCounter >= 4)
278 {
279 trailingPtr++;
280 }
281 }
282 counter = 1;
283 char *contentLength = strstr(initialReadBuffer, "Content-Length: ");
284 if (contentLength == NULL)
285 {
286 wpi_setWPIErrorWithContext(IncompatibleMode, "No content-length token found in packet");
287 close(m_cameraSocket);
288 return ERROR;
289 }
290 contentLength = contentLength + 16; // skip past "content length"
291 int readLength = atol(contentLength); // get the image byte count
292
293 // Make sure buffer is large enough
294 if (imgBufferLength < readLength)
295 {
296 if (imgBuffer) delete[] imgBuffer;
297 imgBufferLength = readLength + kImageBufferAllocationIncrement;
298 imgBuffer = new char[imgBufferLength];
299 if (imgBuffer == NULL)
300 {
301 imgBufferLength = 0;
302 continue;
303 }
304 }
305
306 // Read the image data for "Content-Length" bytes
307 int bytesRead = 0;
308 int remaining = readLength;
309 while(bytesRead < readLength)
310 {
311 int bytesThisRecv = recv(m_cameraSocket, &imgBuffer[bytesRead], remaining, 0);
312 bytesRead += bytesThisRecv;
313 remaining -= bytesThisRecv;
314 }
315 // Update image
316 UpdatePublicImageFromCamera(imgBuffer, readLength);
317 if (semTake(m_paramChangedSem, NO_WAIT) == OK)
318 {
319 // params need to be updated: close the video stream; release the camera.
320 close(m_cameraSocket);
321 semGive(m_socketPossessionSem);
322 return 0;
323 }
324 }
325}
326
327/**
328 * Copy the image from private buffer to shared buffer.
329 * @param imgBuffer The buffer containing the image
330 * @param bufLength The length of the image
331 */
332void AxisCamera::UpdatePublicImageFromCamera(char *imgBuffer, int imgSize)
333{
334 {
335 Synchronized sync(m_protectedImageSem);
336
337 // Adjust the buffer size if current destination buffer is too small.
338 if (m_protectedImageBufferLength < imgSize)
339 {
340 if (m_protectedImageBuffer != NULL) delete [] m_protectedImageBuffer;
341 m_protectedImageBufferLength = imgSize + kImageBufferAllocationIncrement;
342 m_protectedImageBuffer = new char[m_protectedImageBufferLength];
343 if (m_protectedImageBuffer == NULL)
344 {
345 m_protectedImageBufferLength = 0;
346 return;
347 }
348 }
349
350 memcpy(m_protectedImageBuffer, imgBuffer, imgSize);
351 m_protectedImageSize = imgSize;
352 }
353
354 m_freshImage = true;
355 // Notify everyone who is interested.
356 SemSet_t::iterator it = m_newImageSemSet.begin();
357 SemSet_t::iterator end = m_newImageSemSet.end();
358 for (;it != end; it++)
359 {
360 semGive(*it);
361 }
362}
363
364/**
365 * Implement the pure virtual interface so that when parameter changes require a restart, the image task can be bounced.
366 */
367void AxisCamera::RestartCameraTask()
368{
369 m_imageStreamTask.Stop();
370 m_imageStreamTask.Start((int)this);
371}
372
373#if JAVA_CAMERA_LIB == 1
374
375// C bindings used by Java
376// These need to stay as is or Java has to change
377
378void AxisCameraStart(const char *IPAddress)
379{
380#ifdef SVN_REV
381 if (strlen(SVN_REV))
382 {
383 printf("JavaCameraLib was compiled from SVN revision %s\n", SVN_REV);
384 }
385 else
386 {
387 printf("JavaCameraLib was compiled from a location that is not source controlled.\n");
388 }
389#else
390 printf("JavaCameraLib was compiled without -D'SVN_REV=nnnn'\n");
391#endif
392 AxisCamera::GetInstance(IPAddress);
393}
394
395int AxisCameraGetImage (Image* image)
396{
397 return AxisCamera::GetInstance().GetImage(image);
398}
399
400void AxisCameraWriteBrightness(int brightness)
401{
402 AxisCamera::GetInstance().WriteBrightness(brightness);
403}
404
405int AxisCameraGetBrightness()
406{
407 return AxisCamera::GetInstance().GetBrightness();
408}
409
410void AxisCameraWriteWhiteBalance(AxisCameraParams::WhiteBalance_t whiteBalance)
411{
412 AxisCamera::GetInstance().WriteWhiteBalance(whiteBalance);
413}
414
415AxisCameraParams::WhiteBalance_t AxisCameraGetWhiteBalance()
416{
417 return AxisCamera::GetInstance().GetWhiteBalance();
418}
419
420void AxisCameraWriteColorLevel(int colorLevel)
421{
422 AxisCamera::GetInstance().WriteColorLevel(colorLevel);
423}
424
425int AxisCameraGetColorLevel()
426{
427 return AxisCamera::GetInstance().GetColorLevel();
428}
429
430void AxisCameraWriteExposureControl(AxisCameraParams::Exposure_t exposure)
431{
432 AxisCamera::GetInstance().WriteExposureControl(exposure);
433}
434
435AxisCameraParams::Exposure_t AxisCameraGetExposureControl()
436{
437 return AxisCamera::GetInstance().GetExposureControl();
438}
439
440void AxisCameraWriteExposurePriority(int exposure)
441{
442 AxisCamera::GetInstance().WriteExposurePriority(exposure);
443}
444
445int AxisCameraGetExposurePriority()
446{
447 return AxisCamera::GetInstance().GetExposurePriority();
448}
449
450void AxisCameraWriteMaxFPS(int maxFPS)
451{
452 AxisCamera::GetInstance().WriteMaxFPS(maxFPS);
453}
454
455int AxisCameraGetMaxFPS()
456{
457 return AxisCamera::GetInstance().GetMaxFPS();
458}
459
460void AxisCameraWriteResolution(AxisCameraParams::Resolution_t resolution)
461{
462 AxisCamera::GetInstance().WriteResolution(resolution);
463}
464
465AxisCameraParams::Resolution_t AxisCameraGetResolution()
466{
467 return AxisCamera::GetInstance().GetResolution();
468}
469
470void AxisCameraWriteCompression(int compression)
471{
472 AxisCamera::GetInstance().WriteCompression(compression);
473}
474
475int AxisCameraGetCompression()
476{
477 return AxisCamera::GetInstance().GetCompression();
478}
479
480void AxisCameraWriteRotation(AxisCameraParams::Rotation_t rotation)
481{
482 AxisCamera::GetInstance().WriteRotation(rotation);
483}
484
485AxisCameraParams::Rotation_t AxisCameraGetRotation()
486{
487 return AxisCamera::GetInstance().GetRotation();
488}
489
490void AxisCameraDeleteInstance()
491{
492 AxisCamera::DeleteInstance();
493}
494
495int AxisCameraFreshImage()
496{
497 return AxisCamera::GetInstance().IsFreshImage();
498}
499
500#endif // JAVA_CAMERA_LIB == 1
501