blob: 14aa4b3095f8e5fa24079d817ab376b1f650b9fc [file] [log] [blame]
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2008. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */
/*----------------------------------------------------------------------------*/
#include "Vision/AxisCamera.h"
#include <string.h>
#include "NetworkCommunication/UsageReporting.h"
#include "Synchronized.h"
#include "Vision/PCVideoServer.h"
#include "WPIErrors.h"
/** Private NI function to decode JPEG */
IMAQ_FUNC int Priv_ReadJPEGString_C(Image* _image, const unsigned char* _string, UINT32 _stringLength);
// Max packet without jumbo frames is 1500... add 36 because??
#define kMaxPacketSize 1536
#define kImageBufferAllocationIncrement 1000
AxisCamera *AxisCamera::_instance = NULL;
/**
* AxisCamera constructor
*/
AxisCamera::AxisCamera(const char *ipAddress)
: AxisCameraParams(ipAddress)
, m_cameraSocket(ERROR)
, m_protectedImageBuffer(NULL)
, m_protectedImageBufferLength(0)
, m_protectedImageSize(0)
, m_protectedImageSem(NULL)
, m_freshImage(false)
, m_imageStreamTask("cameraTask", (FUNCPTR)s_ImageStreamTaskFunction)
, m_videoServer(NULL)
{
m_protectedImageSem = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE | SEM_DELETE_SAFE);
#if JAVA_CAMERA_LIB != 1
nUsageReporting::report(nUsageReporting::kResourceType_AxisCamera, ipAddress == NULL ? 1 : 2);
#endif
if (!StatusIsFatal())
m_imageStreamTask.Start((int)this);
}
/**
* Destructor
*/
AxisCamera::~AxisCamera()
{
delete m_videoServer;
m_videoServer = NULL;
m_imageStreamTask.Stop();
close(m_cameraSocket);
SemSet_t::iterator it = m_newImageSemSet.begin();
SemSet_t::iterator end = m_newImageSemSet.end();
for (;it != end; it++)
{
semDelete(*it);
}
m_newImageSemSet.clear();
semDelete(m_protectedImageSem);
}
/**
* Get a pointer to the AxisCamera object, if the object does not exist, create it
* To use the camera on port 2 of a cRIO-FRC, pass "192.168.0.90" to the first GetInstance call.
* @return reference to AxisCamera object
*/
AxisCamera &AxisCamera::GetInstance(const char *cameraIP)
{
if (NULL == _instance)
{
_instance = new AxisCamera(cameraIP);
_instance->m_videoServer = new PCVideoServer();
}
return *_instance;
}
/**
* Called by Java to delete the camera... how thoughtful
*/
void AxisCamera::DeleteInstance()
{
delete _instance;
_instance = NULL;
}
/**
* Return true if the latest image from the camera has not been retrieved by calling GetImage() yet.
* @return true if the image has not been retrieved yet.
*/
bool AxisCamera::IsFreshImage()
{
return m_freshImage;
}
/**
* Get the semaphore to be used to synchronize image access with camera acquisition
*
* Call semTake on the returned semaphore to block until a new image is acquired.
*
* The semaphore is owned by the AxisCamera class and will be deleted when the class is destroyed.
* @return A semaphore to notify when new image is received
*/
SEM_ID AxisCamera::GetNewImageSem()
{
SEM_ID sem = semBCreate (SEM_Q_PRIORITY, SEM_EMPTY);
m_newImageSemSet.insert(sem);
return sem;
}
/**
* Get an image from the camera and store it in the provided image.
* @param image The imaq image to store the result in. This must be an HSL or RGB image
* This function is called by Java.
* @return 1 upon success, zero on a failure
*/
int AxisCamera::GetImage(Image* imaqImage)
{
if (m_protectedImageBuffer == NULL)
return 0;
Synchronized sync(m_protectedImageSem);
Priv_ReadJPEGString_C(imaqImage,
(unsigned char*)m_protectedImageBuffer, m_protectedImageSize);
m_freshImage = false;
return 1;
}
#if JAVA_CAMERA_LIB != 1
/**
* Get an image from the camera and store it in the provided image.
* @param image The image to store the result in. This must be an HSL or RGB image
* @return 1 upon success, zero on a failure
*/
int AxisCamera::GetImage(ColorImage* image)
{
return GetImage(image->GetImaqImage());
}
/**
* Instantiate a new image object and fill it with the latest image from the camera.
*
* The returned pointer is owned by the caller and is their responsibility to delete.
* @return a pointer to an HSLImage object
*/
HSLImage* AxisCamera::GetImage()
{
HSLImage *image = new HSLImage();
GetImage(image);
return image;
}
#endif
/**
* Copy an image into an existing buffer.
* This copies an image into an existing buffer rather than creating a new image
* in memory. That way a new image is only allocated when the image being copied is
* larger than the destination.
* This method is called by the PCVideoServer class.
* @param imageData The destination image.
* @param numBytes The size of the destination image.
* @return 0 if failed (no source image or no memory), 1 if success.
*/
int AxisCamera::CopyJPEG(char **destImage, int &destImageSize, int &destImageBufferSize)
{
Synchronized sync(m_protectedImageSem);
if (destImage == NULL)
wpi_setWPIErrorWithContext(NullParameter, "destImage must not be NULL");
if (m_protectedImageBuffer == NULL || m_protectedImageSize <= 0)
return 0; // if no source image
if (destImageBufferSize < m_protectedImageSize) // if current destination buffer too small
{
if (*destImage != NULL) delete [] *destImage;
destImageBufferSize = m_protectedImageSize + kImageBufferAllocationIncrement;
*destImage = new char[destImageBufferSize];
if (*destImage == NULL) return 0;
}
// copy this image into destination buffer
if (*destImage == NULL)
{
wpi_setWPIErrorWithContext(NullParameter, "*destImage must not be NULL");
}
// TODO: Is this copy realy necessary... perhaps we can simply transmit while holding the protected buffer
memcpy(*destImage, m_protectedImageBuffer, m_protectedImageSize);
destImageSize = m_protectedImageSize;
return 1;
}
/**
* Static interface that will cause an instantiation if necessary.
* This static stub is directly spawned as a task to read images from the camera.
*/
int AxisCamera::s_ImageStreamTaskFunction(AxisCamera *thisPtr)
{
return thisPtr->ImageStreamTaskFunction();
}
/**
* Task spawned by AxisCamera constructor to receive images from cam
* If setNewImageSem has been called, this function does a semGive on each new image
* Images can be accessed by calling getImage()
*/
int AxisCamera::ImageStreamTaskFunction()
{
// Loop on trying to setup the camera connection. This happens in a background
// thread so it shouldn't effect the operation of user programs.
while (1)
{
const char *requestString = "GET /mjpg/video.mjpg HTTP/1.1\n\
User-Agent: HTTPStreamClient\n\
Connection: Keep-Alive\n\
Cache-Control: no-cache\n\
Authorization: Basic RlJDOkZSQw==\n\n";
semTake(m_socketPossessionSem, WAIT_FOREVER);
m_cameraSocket = CreateCameraSocket(requestString);
if (m_cameraSocket == ERROR)
{
// Don't hammer the camera if it isn't ready.
semGive(m_socketPossessionSem);
taskDelay(1000);
}
else
{
ReadImagesFromCamera();
}
}
}
/**
* This function actually reads the images from the camera.
*/
int AxisCamera::ReadImagesFromCamera()
{
char *imgBuffer = NULL;
int imgBufferLength = 0;
//Infinite loop, task deletion handled by taskDeleteHook
// Socket cleanup handled by destructor
// TODO: these recv calls must be non-blocking. Otherwise if the camera
// fails during a read, the code hangs and never retries when the camera comes
// back up.
int counter = 2;
while (1)
{
char initialReadBuffer[kMaxPacketSize] = "";
char intermediateBuffer[1];
char *trailingPtr = initialReadBuffer;
int trailingCounter = 0;
while (counter)
{
// TODO: fix me... this cannot be the most efficient way to approach this, reading one byte at a time.
if(recv(m_cameraSocket, intermediateBuffer, 1, 0) == ERROR)
{
wpi_setErrnoErrorWithContext("Failed to read image header");
close (m_cameraSocket);
return ERROR;
}
strncat(initialReadBuffer, intermediateBuffer, 1);
// trailingCounter ensures that we start looking for the 4 byte string after
// there is at least 4 bytes total. Kind of obscure.
// look for 2 blank lines (\r\n)
if (NULL != strstr(trailingPtr, "\r\n\r\n"))
{
--counter;
}
if (++trailingCounter >= 4)
{
trailingPtr++;
}
}
counter = 1;
char *contentLength = strstr(initialReadBuffer, "Content-Length: ");
if (contentLength == NULL)
{
wpi_setWPIErrorWithContext(IncompatibleMode, "No content-length token found in packet");
close(m_cameraSocket);
return ERROR;
}
contentLength = contentLength + 16; // skip past "content length"
int readLength = atol(contentLength); // get the image byte count
// Make sure buffer is large enough
if (imgBufferLength < readLength)
{
if (imgBuffer) delete[] imgBuffer;
imgBufferLength = readLength + kImageBufferAllocationIncrement;
imgBuffer = new char[imgBufferLength];
if (imgBuffer == NULL)
{
imgBufferLength = 0;
continue;
}
}
// Read the image data for "Content-Length" bytes
int bytesRead = 0;
int remaining = readLength;
while(bytesRead < readLength)
{
int bytesThisRecv = recv(m_cameraSocket, &imgBuffer[bytesRead], remaining, 0);
bytesRead += bytesThisRecv;
remaining -= bytesThisRecv;
}
// Update image
UpdatePublicImageFromCamera(imgBuffer, readLength);
if (semTake(m_paramChangedSem, NO_WAIT) == OK)
{
// params need to be updated: close the video stream; release the camera.
close(m_cameraSocket);
semGive(m_socketPossessionSem);
return 0;
}
}
}
/**
* Copy the image from private buffer to shared buffer.
* @param imgBuffer The buffer containing the image
* @param bufLength The length of the image
*/
void AxisCamera::UpdatePublicImageFromCamera(char *imgBuffer, int imgSize)
{
{
Synchronized sync(m_protectedImageSem);
// Adjust the buffer size if current destination buffer is too small.
if (m_protectedImageBufferLength < imgSize)
{
if (m_protectedImageBuffer != NULL) delete [] m_protectedImageBuffer;
m_protectedImageBufferLength = imgSize + kImageBufferAllocationIncrement;
m_protectedImageBuffer = new char[m_protectedImageBufferLength];
if (m_protectedImageBuffer == NULL)
{
m_protectedImageBufferLength = 0;
return;
}
}
memcpy(m_protectedImageBuffer, imgBuffer, imgSize);
m_protectedImageSize = imgSize;
}
m_freshImage = true;
// Notify everyone who is interested.
SemSet_t::iterator it = m_newImageSemSet.begin();
SemSet_t::iterator end = m_newImageSemSet.end();
for (;it != end; it++)
{
semGive(*it);
}
}
/**
* Implement the pure virtual interface so that when parameter changes require a restart, the image task can be bounced.
*/
void AxisCamera::RestartCameraTask()
{
m_imageStreamTask.Stop();
m_imageStreamTask.Start((int)this);
}
#if JAVA_CAMERA_LIB == 1
// C bindings used by Java
// These need to stay as is or Java has to change
void AxisCameraStart(const char *IPAddress)
{
#ifdef SVN_REV
if (strlen(SVN_REV))
{
printf("JavaCameraLib was compiled from SVN revision %s\n", SVN_REV);
}
else
{
printf("JavaCameraLib was compiled from a location that is not source controlled.\n");
}
#else
printf("JavaCameraLib was compiled without -D'SVN_REV=nnnn'\n");
#endif
AxisCamera::GetInstance(IPAddress);
}
int AxisCameraGetImage (Image* image)
{
return AxisCamera::GetInstance().GetImage(image);
}
void AxisCameraWriteBrightness(int brightness)
{
AxisCamera::GetInstance().WriteBrightness(brightness);
}
int AxisCameraGetBrightness()
{
return AxisCamera::GetInstance().GetBrightness();
}
void AxisCameraWriteWhiteBalance(AxisCameraParams::WhiteBalance_t whiteBalance)
{
AxisCamera::GetInstance().WriteWhiteBalance(whiteBalance);
}
AxisCameraParams::WhiteBalance_t AxisCameraGetWhiteBalance()
{
return AxisCamera::GetInstance().GetWhiteBalance();
}
void AxisCameraWriteColorLevel(int colorLevel)
{
AxisCamera::GetInstance().WriteColorLevel(colorLevel);
}
int AxisCameraGetColorLevel()
{
return AxisCamera::GetInstance().GetColorLevel();
}
void AxisCameraWriteExposureControl(AxisCameraParams::Exposure_t exposure)
{
AxisCamera::GetInstance().WriteExposureControl(exposure);
}
AxisCameraParams::Exposure_t AxisCameraGetExposureControl()
{
return AxisCamera::GetInstance().GetExposureControl();
}
void AxisCameraWriteExposurePriority(int exposure)
{
AxisCamera::GetInstance().WriteExposurePriority(exposure);
}
int AxisCameraGetExposurePriority()
{
return AxisCamera::GetInstance().GetExposurePriority();
}
void AxisCameraWriteMaxFPS(int maxFPS)
{
AxisCamera::GetInstance().WriteMaxFPS(maxFPS);
}
int AxisCameraGetMaxFPS()
{
return AxisCamera::GetInstance().GetMaxFPS();
}
void AxisCameraWriteResolution(AxisCameraParams::Resolution_t resolution)
{
AxisCamera::GetInstance().WriteResolution(resolution);
}
AxisCameraParams::Resolution_t AxisCameraGetResolution()
{
return AxisCamera::GetInstance().GetResolution();
}
void AxisCameraWriteCompression(int compression)
{
AxisCamera::GetInstance().WriteCompression(compression);
}
int AxisCameraGetCompression()
{
return AxisCamera::GetInstance().GetCompression();
}
void AxisCameraWriteRotation(AxisCameraParams::Rotation_t rotation)
{
AxisCamera::GetInstance().WriteRotation(rotation);
}
AxisCameraParams::Rotation_t AxisCameraGetRotation()
{
return AxisCamera::GetInstance().GetRotation();
}
void AxisCameraDeleteInstance()
{
AxisCamera::DeleteInstance();
}
int AxisCameraFreshImage()
{
return AxisCamera::GetInstance().IsFreshImage();
}
#endif // JAVA_CAMERA_LIB == 1