X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FNetwork%2Fjpg-httpd.cxx;h=b3171827cc75d3c319aeb3afa4914550abbfdf2c;hb=efcc89480db17c181262aca1d6001ba9d92f0867;hp=63afbffbdcd841cd3fa9cef01d51a65456355b1e;hpb=e75f6a8f0100f847cbe1bb8fc83a0c466dbe44f5;p=flightgear.git diff --git a/src/Network/jpg-httpd.cxx b/src/Network/jpg-httpd.cxx index 63afbffbd..b3171827c 100644 --- a/src/Network/jpg-httpd.cxx +++ b/src/Network/jpg-httpd.cxx @@ -1,9 +1,8 @@ -// 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. // -// Copyright (C) 2001 Curtis L. Olson - curt@flightgear.org +// Copyright (C) 2001 Curtis L. Olson - http://www.flightgear.org/~curt // // Jpeg Image Support added August 2001 // by Norman Vine - nhv@cape.com @@ -20,7 +19,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // $Id$ @@ -31,96 +30,539 @@ #include -#include // atoi() atof() +#include // atoi() atof() +#include -#include STL_STRING -#include STL_STRSTREAM +#include +#include +#include +#include #include #include #include -#include +#include +#include +#include +#include #include
#include
+#include #include "jpg-httpd.hxx" -SG_USING_STD(string); -#if !defined(SG_HAVE_NATIVE_SGI_COMPILERS) -SG_USING_STD(cout); -SG_USING_STD(istrstream); -#endif +#define HTTP_GET_STRING "GET " +#define HTTP_TERMINATOR "\r\n" +using std::string; -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 +////////////////////////////////////////////////////////////// + +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 _image; + std::string _compressedData; +}; - imageServer = new HttpdImageServer( port ); +typedef SGSharedPtr 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) + { + } - set_hz( 5 ); // default to processing requests @ 5Hz - set_enabled( true ); + 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; +}; - return true; + +/////////////////////////////////////////////////////////////////////////// + +class WindowCaptureCallback : public osg::Camera::DrawCallback +{ +public: + + 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 > 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; +}; + +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) + { + 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 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); +} -bool FGJpegHttpd::close() { - delete imageServer; +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 ); +} - return true; +////////////////////////////////////////////////////////////// +// class FGJpegHttpd +////////////////////////////////////////////////////////////// + +FGJpegHttpd::FGJpegHttpd( int p, int hz, const std::string& type ) +{ + _imageServer.reset(new HttpdThread( p, hz, type )); } +FGJpegHttpd::~FGJpegHttpd() +{ +} -// Handle http GET requests -void HttpdImageChannel::foundTerminator (void) { +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; + } - closeWhenDone (); + if (!_imageServer->init()) { + SG_LOG( SG_IO, SG_ALERT, "FGJpegHttpd: failed to init Http sevrer thread"); + return false; + } + + _imageServer->start(); + set_enabled( true ); - string response; + return true; +} - const string s = buffer.getData(); - if ( s.find( "GET " ) == 0 ) { - - printf("echo: %s\n", s.c_str()); - - int ImageLen = JpgFactory->render(); - - if( ImageLen ) { - response = "HTTP/1.1 200 OK"; - response += getTerminator(); - response += "Content-Type: image/jpeg"; - response += getTerminator(); - push( response.c_str() ); - - char ctmp[256]; - printf( "info->numbytes = %d\n", ImageLen ); - sprintf( ctmp, "Content-Length: %d", ImageLen ); - push( ctmp ); - - response = getTerminator(); - response += "Connection: close"; - response += getTerminator(); - response += getTerminator(); - push( response.c_str() ); - - /* can't use strlen on binary data */ - bufferSend ( (char *)JpgFactory->data(), ImageLen ) ; - } else { - printf("!!! NO IMAGE !!!\n\tinfo->numbytes = %d\n", ImageLen ); - } +bool FGJpegHttpd::process() +{ + return true; +} + +bool FGJpegHttpd::close() +{ + _imageServer->shutdown(); + return true; +} + +void HttpdImageChannel::processRequest() +{ + std::ostringstream ss; + _bytesToSend = _imageData->size(); + if (_bytesToSend <= 0) { + return; + } + + // 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(); +} + +void HttpdImageChannel::sendResponse() +{ + const char* ptr = _imageData->data(); - buffer.remove(); + 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 + } } +