X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FNetwork%2Fjpg-httpd.cxx;h=b3171827cc75d3c319aeb3afa4914550abbfdf2c;hb=efcc89480db17c181262aca1d6001ba9d92f0867;hp=0c84f5b7d93ccaa863c99c7ee96ca377fe5856c0;hpb=acddd0b0cf9dd70b7cbdb25cc322dc0e3a2689ee;p=flightgear.git diff --git a/src/Network/jpg-httpd.cxx b/src/Network/jpg-httpd.cxx index 0c84f5b7d..b3171827c 100644 --- a/src/Network/jpg-httpd.cxx +++ b/src/Network/jpg-httpd.cxx @@ -31,15 +31,20 @@ #include #include // atoi() atof() - #include +#include +#include +#include +#include + #include #include #include #include #include -#include +#include +#include #include
#include
@@ -47,284 +52,517 @@ #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 -#include -extern osg::ref_ptr sceneView; +#define HTTP_GET_STRING "GET " +#define HTTP_TERMINATOR "\r\n" using std::string; -/* simple httpd server that makes an hasty stab at following the http - 1.1 rfc. */ - ////////////////////////////////////////////////////////////// -// class HttpdImageChannel +// class CompressedImageBuffer ////////////////////////////////////////////////////////////// -class HttpdImageChannel : public simgear::NetChat +class CompressedImageBuffer : public SGReferenced { - - simgear::NetBuffer buffer; - trJpgFactory *JpgFactory; - public: - - HttpdImageChannel() : buffer(512) { - - int nWidth = fgGetInt( "/sim/startup/xsize", 800 ); - int nHeight = fgGetInt( "/sim/startup/ysize", 600 ); - - setTerminator("\r\n"); - JpgFactory = new trJpgFactory(); - int error = JpgFactory -> init( nWidth, nHeight ); - if (0 != error) - { - SG_LOG( SG_IO, SG_ALERT, "Failed to initialize JPEG-factory, error: " << error); + 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; } } - - ~HttpdImageChannel() { - JpgFactory -> destroy(); - delete JpgFactory; + + size_t size() const + { + return _compressedData.length(); } - - virtual void collectIncomingData (const char* s, int n) { - buffer.append(s,n); + + const char* data() const + { + return _compressedData.data(); } - - // Handle the actual http request - virtual void foundTerminator (void); +private: + osg::ref_ptr _image; + std::string _compressedData; }; -////////////////////////////////////////////////////////////// -// class HttpdImageServer +typedef SGSharedPtr ImageBufferPtr; + ////////////////////////////////////////////////////////////// -class HttpdImageServer : private simgear::NetChannel +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 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 _newFrame; + /// current frame we're serving to new connections + ImageBufferPtr _currentFrame; +}; - virtual void handleAccept (void) { - simgear::IPAddress addr; - int handle = accept ( &addr ); - SG_LOG( SG_IO, SG_INFO, "Client " << addr.getHost() << ":" << addr.getPort() << " connected" ); - HttpdImageChannel *hc = new HttpdImageChannel; - hc->setHandle ( handle ); - - poller.addChannel( hc ); - } +/////////////////////////////////////////////////////////////////////////// - simgear::NetChannelPoller poller; +class WindowCaptureCallback : public osg::Camera::DrawCallback +{ public: - - HttpdImageServer ( int port ) + + enum Mode + { + READ_PIXELS, + SINGLE_PBO, + DOUBLE_PBO, + TRIPLE_PBO + }; + + enum FramePosition + { + START_FRAME, + END_FRAME + }; + + struct ContextData : public osg::Referenced { - if (!open()) + 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) { - SG_LOG( SG_IO, SG_ALERT, "Failed to open HttpdImage port."); - return; + _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); } - - if (0 != bind( "", port )) + + void getSize(osg::GraphicsContext* gc, int& width, int& height) { - SG_LOG( SG_IO, SG_ALERT, "Failed to bind HttpdImage port."); - return; + if (gc->getTraits()) + { + width = gc->getTraits()->width; + height = gc->getTraits()->height; + } } + + void readPixels(); + + typedef std::vector< osg::ref_ptr > 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 lock(_mutex); + osg::ref_ptr& 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 cd = getContextData(gc); + cd->readPixels(); + } + + typedef std::map > ContextDataMap; + + Mode _mode; + FramePosition _position; + GLenum _readBuffer; + mutable OpenThreads::Mutex _mutex; + mutable ContextDataMap _contextDataMap; + HttpdThread* _thread; +}; - if (0 != listen( 5 )) +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; +} + +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) { - SG_LOG( SG_IO, SG_ALERT, "Failed to listen on HttpdImage port."); - return; + 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 +////////////////////////////////////////////////////////////// - poller.addChannel(this); - SG_LOG(SG_IO, SG_ALERT, "HttpdImage server started on port " << port); +class HttpdImageChannel : public simgear::NetChannel +{ +public: + HttpdImageChannel(SGSharedPtr img) : + _imageData(img), + _bytesToSend(0) + { + } + + 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(); + } + } } - void poll() + virtual void handleWrite() { - poller.poll(); + 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 frameToWriteOut; + { + SGGuard 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 ); +} + ////////////////////////////////////////////////////////////// // class FGJpegHttpd ////////////////////////////////////////////////////////////// -FGJpegHttpd::FGJpegHttpd( int p ) : - port(p), - imageServer(NULL) +FGJpegHttpd::FGJpegHttpd( int p, int hz, const std::string& type ) { + _imageServer.reset(new HttpdThread( p, hz, type )); } FGJpegHttpd::~FGJpegHttpd() { - delete imageServer; } -bool FGJpegHttpd::open() { +bool FGJpegHttpd::open() +{ if ( is_enabled() ) { - SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel " + SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel " << "is already in use, ignoring" ); - return false; + return false; } - imageServer = new HttpdImageServer( port ); + if (!_imageServer->init()) { + SG_LOG( SG_IO, SG_ALERT, "FGJpegHttpd: failed to init Http sevrer thread"); + return false; + } - set_hz( 5 ); // default to processing requests @ 5Hz + _imageServer->start(); set_enabled( true ); return true; } - -bool FGJpegHttpd::process() { - imageServer->poll(); - +bool FGJpegHttpd::process() +{ return true; } - -bool FGJpegHttpd::close() { - delete imageServer; - imageServer = NULL; +bool FGJpegHttpd::close() +{ + _imageServer->shutdown(); return true; } -// Handle http GET requests -void HttpdImageChannel :: foundTerminator( void ) { - - closeWhenDone(); - - 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; - +void HttpdImageChannel::processRequest() +{ + std::ostringstream ss; + _bytesToSend = _imageData->size(); + if (_bytesToSend <= 0) { + return; + } - if ( strstr( pRequest, __HTTP_GET_STRING ) != NULL ) + // 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, "<<<<<<<<< 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 - } - - SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< End of image Transmission.\n" ); + 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(); +} - } else { - SG_LOG( SG_IO, SG_ALERT, "Failed to generate JPEG image data. Error: " << nImageLen); - } +void HttpdImageChannel::sendResponse() +{ + const char* ptr = _imageData->data(); - /* - * Release JPEG buffer. - */ - JpgFactory -> destroy(); + 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 } - - buffer.remove(); } +