-// httpd.hxx -- FGFS http property manager interface / external script
-// and control class
+// jpg-httpd.cxx -- FGFS jpg-http interface
//
// Written by Curtis Olson, started June 2001.
//
#include <simgear/compiler.h>
-#include <stdlib.h> // atoi() atof()
+#include <cstdlib> // atoi() atof()
+#include <cstring>
-#include <string>
+#include <osgDB/Registry>
+#include <osgDB/ReaderWriter>
+#include <osgUtil/SceneView>
+#include <osgViewer/Viewer>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iochannel.hxx>
#include <simgear/math/sg_types.hxx>
#include <simgear/props/props.hxx>
+#include <simgear/io/sg_netChat.hxx>
+#include <simgear/threads/SGThread.hxx>
+#include <simgear/threads/SGGuard.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
-#include <Main/renderer.hxx>
+#include <Viewer/renderer.hxx>
#include "jpg-httpd.hxx"
-#define __MAX_HTTP_BLOCK_SIZE 4096
-#define __MAX_STRING_SIZE 2048
-#define __TIMEOUT_COUNT 5
-#define __HTTP_GET_STRING "GET "
-
-#include <osgUtil/SceneView>
-#include <osgViewer/Viewer>
-extern osg::ref_ptr<osgUtil::SceneView> sceneView;
+#define HTTP_GET_STRING "GET "
+#define HTTP_TERMINATOR "\r\n"
using std::string;
+//////////////////////////////////////////////////////////////
+// class CompressedImageBuffer
+//////////////////////////////////////////////////////////////
-bool FGJpegHttpd::open() {
- if ( is_enabled() ) {
- SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
- << "is already in use, ignoring" );
- return false;
+class CompressedImageBuffer : public SGReferenced
+{
+public:
+ CompressedImageBuffer(osg::Image* img) :
+ _image(img)
+ {
+ }
+
+ bool compress(const std::string& extension)
+ {
+ osgDB::ReaderWriter* writer =
+ osgDB::Registry::instance()->getReaderWriterForExtension(extension);
+
+ std::stringstream outputStream;
+ osgDB::ReaderWriter::WriteResult wr;
+ wr = writer->writeImage(*_image,outputStream, NULL);
+
+ if(wr.success()) {
+ _compressedData = outputStream.str();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ size_t size() const
+ {
+ return _compressedData.length();
+ }
+
+ const char* data() const
+ {
+ return _compressedData.data();
}
+private:
+ osg::ref_ptr<osg::Image> _image;
+ std::string _compressedData;
+};
+
+typedef SGSharedPtr<CompressedImageBuffer> ImageBufferPtr;
+
+//////////////////////////////////////////////////////////////
+
+class HttpdThread : public SGThread, private simgear::NetChannel
+{
+public:
+ HttpdThread (int port, int frameHz, const std::string& type) :
+ _done(false),
+ _port(port),
+ _hz(frameHz),
+ _imageType(type)
+ {
+ }
+
+ bool init();
+ void shutdown();
+
+ virtual void run();
+
+ void setNewFrameImage(osg::Image* frameImage)
+ {
+ SGGuard<SGMutex> g(_newFrameLock);
+ _newFrame = frameImage;
+ }
+
+ int getFrameHz() const
+ { return _hz; }
+
+ void setDone()
+ {
+ _done = true;
+ }
+private:
+ virtual bool writable (void) { return false; }
+
+ virtual void handleAccept (void);
+
+ bool _done;
+ simgear::NetChannelPoller _poller;
+ int _port, _hz;
+ std::string _imageType;
+
+ SGMutex _newFrameLock;
+ osg::ref_ptr<osg::Image> _newFrame;
+ /// current frame we're serving to new connections
+ ImageBufferPtr _currentFrame;
+};
+
- imageServer = new HttpdImageServer( port );
+///////////////////////////////////////////////////////////////////////////
+
+class WindowCaptureCallback : public osg::Camera::DrawCallback
+{
+public:
- set_hz( 5 ); // default to processing requests @ 5Hz
- set_enabled( true );
+ enum Mode
+ {
+ READ_PIXELS,
+ SINGLE_PBO,
+ DOUBLE_PBO,
+ TRIPLE_PBO
+ };
+
+ enum FramePosition
+ {
+ START_FRAME,
+ END_FRAME
+ };
+
+ struct ContextData : public osg::Referenced
+ {
+ ContextData(osg::GraphicsContext* gc, Mode mode, GLenum readBuffer, HttpdThread* httpd):
+ _gc(gc),
+ _mode(mode),
+ _readBuffer(readBuffer),
+ _pixelFormat(GL_RGB),
+ _type(GL_UNSIGNED_BYTE),
+ _width(0),
+ _height(0),
+ _currentImageIndex(0),
+ _httpd(httpd)
+ {
+ _previousFrameTick = osg::Timer::instance()->tick();
+
+ if (gc->getTraits() && gc->getTraits()->alpha) {
+ _pixelFormat = GL_RGBA;
+ }
+
+ getSize(gc, _width, _height);
+
+ // single buffered image
+ _imageBuffer.push_back(new osg::Image);
+ }
+
+ void getSize(osg::GraphicsContext* gc, int& width, int& height)
+ {
+ if (gc->getTraits())
+ {
+ width = gc->getTraits()->width;
+ height = gc->getTraits()->height;
+ }
+ }
+
+ void readPixels();
+
+ typedef std::vector< osg::ref_ptr<osg::Image> > ImageBuffer;
+
+ osg::GraphicsContext* _gc;
+ Mode _mode;
+ GLenum _readBuffer;
+ GLenum _pixelFormat;
+ GLenum _type;
+ int _width;
+ int _height;
+ unsigned int _currentImageIndex;
+ ImageBuffer _imageBuffer;
+ osg::Timer_t _previousFrameTick;
+ HttpdThread* _httpd;
+ };
+
+ WindowCaptureCallback(HttpdThread *thread, Mode mode, FramePosition position, GLenum readBuffer):
+ _mode(mode),
+ _position(position),
+ _readBuffer(readBuffer),
+ _thread(thread)
+ {
+ }
+
+ FramePosition getFramePosition() const { return _position; }
+
+ ContextData* createContextData(osg::GraphicsContext* gc) const
+ {
+ return new ContextData(gc, _mode, _readBuffer, _thread);
+ }
+
+ ContextData* getContextData(osg::GraphicsContext* gc) const
+ {
+ OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
+ osg::ref_ptr<ContextData>& data = _contextDataMap[gc];
+ if (!data) data = createContextData(gc);
+
+ return data.get();
+ }
+
+ virtual void operator () (osg::RenderInfo& renderInfo) const
+ {
+ glReadBuffer(_readBuffer);
+
+ osg::GraphicsContext* gc = renderInfo.getState()->getGraphicsContext();
+ osg::ref_ptr<ContextData> cd = getContextData(gc);
+ cd->readPixels();
+ }
+
+ typedef std::map<osg::GraphicsContext*, osg::ref_ptr<ContextData> > ContextDataMap;
+
+ Mode _mode;
+ FramePosition _position;
+ GLenum _readBuffer;
+ mutable OpenThreads::Mutex _mutex;
+ mutable ContextDataMap _contextDataMap;
+ HttpdThread* _thread;
+};
+
+void WindowCaptureCallback::ContextData::readPixels()
+{
+ osg::Timer_t n = osg::Timer::instance()->tick();
+ double dt = osg::Timer::instance()->delta_s(n, _previousFrameTick);
+ double frameInterval = 1.0 / _httpd->getFrameHz();
+ if (dt < frameInterval)
+ return;
+
+ _previousFrameTick = n;
+ unsigned int nextImageIndex = (_currentImageIndex+1)%_imageBuffer.size();
+
+ int width=0, height=0;
+ getSize(_gc, width, height);
+ if (width!=_width || _height!=height)
+ {
+ _width = width;
+ _height = height;
+ }
+
+ osg::Image* image = _imageBuffer[_currentImageIndex].get();
+ image->readPixels(0,0,_width,_height,
+ _pixelFormat,_type);
+
+ _httpd->setNewFrameImage(image);
+ _currentImageIndex = nextImageIndex;
+}
- return true;
+osg::Camera* findLastCamera(osgViewer::ViewerBase& viewer)
+{
+ osgViewer::ViewerBase::Windows windows;
+ viewer.getWindows(windows);
+ for(osgViewer::ViewerBase::Windows::iterator itr = windows.begin();
+ itr != windows.end();
+ ++itr)
+ {
+ osgViewer::GraphicsWindow* window = *itr;
+ osg::GraphicsContext::Cameras& cameras = window->getCameras();
+ osg::Camera* lastCamera = 0;
+ for(osg::GraphicsContext::Cameras::iterator cam_itr = cameras.begin();
+ cam_itr != cameras.end();
+ ++cam_itr)
+ {
+ if (lastCamera)
+ {
+ if ((*cam_itr)->getRenderOrder() > lastCamera->getRenderOrder())
+ {
+ lastCamera = (*cam_itr);
+ }
+ if ((*cam_itr)->getRenderOrder() == lastCamera->getRenderOrder() &&
+ (*cam_itr)->getRenderOrderNum() >= lastCamera->getRenderOrderNum())
+ {
+ lastCamera = (*cam_itr);
+ }
+ }
+ else
+ {
+ lastCamera = *cam_itr;
+ }
+ }
+
+ return lastCamera;
+ }
+
+ return NULL;
}
+//////////////////////////////////////////////////////////////
+// class HttpdImageChannel
+//////////////////////////////////////////////////////////////
+
+class HttpdImageChannel : public simgear::NetChannel
+{
+public:
+ HttpdImageChannel(SGSharedPtr<CompressedImageBuffer> img) :
+ _imageData(img),
+ _bytesToSend(0)
+ {
+ }
-bool FGJpegHttpd::process() {
- netChannel::poll();
+ void setMimeType(const std::string& s)
+ {
+ _mimeType = s;
+ }
+
+ virtual void collectIncomingData (const char* s, int n)
+ {
+ _request += string(s, n);
+ }
+
+ virtual void handleRead()
+ {
+ char data[512];
+ int num_read = recv(data, 512);
+ if (num_read > 0) {
+ _request += std::string(data, num_read);
+ }
+
+ if (_request.find(HTTP_TERMINATOR) != std::string::npos) {
+ // have complete first line of request
+ if (_request.find(HTTP_GET_STRING) == 0) {
+ processRequest();
+ }
+ }
+ }
+ virtual void handleWrite()
+ {
+ sendResponse();
+ }
+
+private:
+ void processRequest();
+ void sendResponse();
+
+ std::string _request;
+ ImageBufferPtr _imageData;
+ std::string _mimeType;
+ size_t _bytesToSend;
+};
+
+//////////////////////////////////////////////////////////////
+// class HttpdThread
+//////////////////////////////////////////////////////////////
+
+bool HttpdThread::init()
+{
+ if (!open()) {
+ SG_LOG( SG_IO, SG_ALERT, "Failed to open HttpdImage port.");
+ return false;
+ }
+
+ if (0 != bind( "", _port )) {
+ SG_LOG( SG_IO, SG_ALERT, "Failed to bind HttpdImage port.");
+ return false;
+ }
+
+ if (0 != listen( 5 )) {
+ SG_LOG( SG_IO, SG_ALERT, "Failed to listen on HttpdImage port.");
+ return false;
+ }
+
+ _poller.addChannel(this);
+
+ osgViewer::Viewer* v = globals->get_renderer()->getViewer();
+ osg::Camera* c = findLastCamera(*v);
+ if (!c) {
+ return false;
+ }
+
+ c->setFinalDrawCallback(new WindowCaptureCallback(this,
+ WindowCaptureCallback::READ_PIXELS,
+ WindowCaptureCallback::END_FRAME,
+ GL_BACK));
+
+
+ SG_LOG(SG_IO, SG_INFO, "HttpdImage server started on port " << _port);
return true;
}
+void HttpdThread::shutdown()
+{
+ setDone();
+ join();
+
+ osgViewer::Viewer* v = globals->get_renderer()->getViewer();
+ osg::Camera* c = findLastCamera(*v);
+ c->setFinalDrawCallback(NULL);
+
+ SG_LOG(SG_IO, SG_INFO, "HttpdImage server shutdown on port " << _port);
+}
+
+void HttpdThread::run()
+{
+ while (!_done) {
+ _poller.poll(10); // 10msec sleep
+
+ // locked section to check for a new raw frame from the callback
+ osg::ref_ptr<osg::Image> frameToWriteOut;
+ {
+ SGGuard<SGMutex> g(_newFrameLock);
+ if (_newFrame) {
+ frameToWriteOut = _newFrame;
+ _newFrame = NULL;
+ }
+ } // of locked section
+
+ // no locking needed on currentFrame; channels run in this thread
+ if (frameToWriteOut) {
+ _currentFrame = ImageBufferPtr(new CompressedImageBuffer(frameToWriteOut));
+ _currentFrame->compress(_imageType);
+ }
+ } // of thread poll loop
+}
+
+void HttpdThread::handleAccept (void)
+{
+ simgear::IPAddress addr;
+ int handle = accept ( &addr );
+ SG_LOG( SG_IO, SG_DEBUG, "Client " << addr.getHost() << ":" << addr.getPort() << " connected" );
+
+ HttpdImageChannel *hc = new HttpdImageChannel(_currentFrame);
+ hc->setMimeType("image/" + _imageType);
+ hc->setHandle ( handle );
+
+ _poller.addChannel( hc );
+}
-bool FGJpegHttpd::close() {
- delete imageServer;
+//////////////////////////////////////////////////////////////
+// class FGJpegHttpd
+//////////////////////////////////////////////////////////////
- return true;
+FGJpegHttpd::FGJpegHttpd( int p, int hz, const std::string& type )
+{
+ _imageServer.reset(new HttpdThread( p, hz, type ));
}
-// Handle http GET requests
-void HttpdImageChannel :: foundTerminator( void ) {
+FGJpegHttpd::~FGJpegHttpd()
+{
+}
- closeWhenDone();
+bool FGJpegHttpd::open()
+{
+ if ( is_enabled() ) {
+ SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
+ << "is already in use, ignoring" );
+ return false;
+ }
- char szTemp[256];
- char szResponse[__MAX_STRING_SIZE];
- char *pRequest = buffer.getData();
- int nStep = 0;
- int nBytesSent = 0;
- int nTimeoutCount = 0;
- int nBufferCount = 0;
- int nImageLen;
- int nBlockSize;
+ if (!_imageServer->init()) {
+ SG_LOG( SG_IO, SG_ALERT, "FGJpegHttpd: failed to init Http sevrer thread");
+ return false;
+ }
+
+ _imageServer->start();
+ set_enabled( true );
+ return true;
+}
- if ( strstr( pRequest, __HTTP_GET_STRING ) != NULL ) {
-
- SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< HTTP Request : " << pRequest );
-
- double left, right, bottom, top, zNear, zFar;
- osgViewer::Viewer* viewer = globals->get_renderer()->getViewer();
- viewer->getCamera()->getProjectionMatrixAsFrustum(left, right,
- bottom, top,
- zNear, zFar);
- JpgFactory->setFrustum( left, right, bottom, top, zNear, zFar );
-
- nImageLen = JpgFactory -> render();
- nBlockSize = ( nImageLen < __MAX_HTTP_BLOCK_SIZE ? nImageLen : __MAX_HTTP_BLOCK_SIZE );
-
- if( nImageLen ) {
- strcpy( szResponse, "HTTP/1.1 200 OK" );
- strcat( szResponse, getTerminator() );
- strcat( szResponse, "Content-Type: image/jpeg" );
- strcat( szResponse, getTerminator() );
-
- SG_LOG( SG_IO, SG_DEBUG, "info->numbytes = " << nImageLen );
- sprintf( szTemp, "Content-Length: %d", nImageLen );
- strcat( szResponse, szTemp );
-
- strcat( szResponse, getTerminator() );
- strcat( szResponse, "Connection: close" );
- strcat( szResponse, getTerminator() );
- strcat( szResponse, getTerminator() );
-
- if( getHandle() == -1 ) {
- SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Invalid socket handle. Ignoring request.\n" );
- buffer.remove();
- SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< End of image Transmission.\n" );
- return;
- }
-
- if( send( ( char * ) szResponse, strlen( szResponse ) ) <= 0 ) {
- SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Error to send HTTP response. Ignoring request.\n" );
- buffer.remove();
- SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< End of image Transmission.\n" );
- return;
- }
-
- /*
- * Send block with size defined by __MAX_HTTP_BLOCK_SIZE
- */
- while( nStep <= nImageLen ) {
- nBufferCount++;
-
- if( getHandle() == -1 ) {
- SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Invalid socket handle. Ignoring request.\n" );
- break;
- }
-
- nBytesSent = send( ( char * ) JpgFactory -> data() + nStep, nBlockSize );
-
- if( nBytesSent <= 0 ) {
- if( nTimeoutCount == __TIMEOUT_COUNT ) {
- SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Timeout reached. Exiting before end of image transmission.\n" );
- nTimeoutCount = 0;
- break;
- }
-
- SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Zero bytes sent.\n" );
-
-#ifdef _WIN32
- Sleep(1000);
-#else
- sleep(1);
-#endif
- nTimeoutCount++;
- continue;
- }
-
- SG_LOG( SG_IO, SG_DEBUG, ">>>>>>>>> (" << nBufferCount << ") BLOCK STEP " << nStep << " - IMAGELEN " << nImageLen << " - BLOCKSIZE " << nBlockSize << " - SENT " << nBytesSent );
-
- /*
- * Calculate remaining image.
- */
- if( ( nStep + nBlockSize ) >= nImageLen ) {
- nBlockSize = ( nImageLen - nStep );
- nStep += nBlockSize;
- }
-
- nStep += nBytesSent;
- nTimeoutCount = 0;
-#ifdef _WIN32
- Sleep(1);
-#else
- usleep( 1000 );
-#endif
- }
+bool FGJpegHttpd::process()
+{
+ return true;
+}
- SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< End of image Transmission.\n" );
+bool FGJpegHttpd::close()
+{
+ _imageServer->shutdown();
+ return true;
+}
- } else {
- SG_LOG( SG_IO, SG_DEBUG, "!!! NO IMAGE !!! info -> numbytes = " << nImageLen );
- }
+void HttpdImageChannel::processRequest()
+{
+ std::ostringstream ss;
+ _bytesToSend = _imageData->size();
+ if (_bytesToSend <= 0) {
+ return;
+ }
- /*
- * Release JPEG buffer.
- */
- JpgFactory -> destroy();
+ // assemble HTTP 1.1 headers. Connection: close is important since we
+ // don't attempt pipelining at all
+ ss << "HTTP/1.1 200 OK" << HTTP_TERMINATOR;
+ ss << "Content-Type: " << _mimeType << HTTP_TERMINATOR;
+ ss << "Content-Length: " << _bytesToSend << HTTP_TERMINATOR;
+ ss << "Connection: close" << HTTP_TERMINATOR;
+ ss << HTTP_TERMINATOR; // end of headers
+
+ if( getHandle() == -1 )
+ {
+ SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Invalid socket handle. Ignoring request.\n" );
+ return;
+ }
+
+ // send headers out
+ string headersData(ss.str());
+ if (send(headersData.c_str(), headersData.length()) <= 0 )
+ {
+ SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Error to send HTTP response. Ignoring request.\n" );
+ return;
}
+
+ _bytesToSend = _imageData->size();
+ sendResponse();
+}
- buffer.remove();
+void HttpdImageChannel::sendResponse()
+{
+ const char* ptr = _imageData->data();
+
+ ptr += (_imageData->size() - _bytesToSend);
+ size_t sent = send(ptr, _bytesToSend);
+ if (sent <= 0) {
+ SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Error to send HTTP response. Ignoring request.\n" );
+ return;
+ }
+
+ _bytesToSend -= sent;
+ if (_bytesToSend == 0) {
+ close();
+ shouldDelete(); // use NetChannelPoller delete mechanism
+ }
}
+