From 1a372d9366550f5fa1deef379129528c9f5692da Mon Sep 17 00:00:00 2001 From: James Turner Date: Thu, 23 Jan 2014 17:41:24 +0000 Subject: [PATCH] Update image-server logic. - no dependency on libJpeg or Simgear - no duplicate rendering of the scene (uses a draw callback) - supports other image types, eg PNG - threaded so doesn't block the main loop ever --- CMakeLists.txt | 21 -- src/GUI/gui_funcs.cxx | 65 +--- src/Include/config_cmake.h.in | 1 - src/Main/fg_io.cxx | 21 +- src/Main/options.cxx | 2 - src/Network/CMakeLists.txt | 7 +- src/Network/jpg-httpd.cxx | 664 +++++++++++++++++++++++----------- src/Network/jpg-httpd.hxx | 11 +- src/Viewer/renderer.cxx | 17 - 9 files changed, 475 insertions(+), 334 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de01dfc14..143793f49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,7 +150,6 @@ option(ENABLE_JSBSIM "Set to ON to build FlightGear with JSBSim FDM (default option(EVENT_INPUT "Set to ON to build FlightGear with event-based Input support" ${EVENT_INPUT_DEFAULT}) option(ENABLE_RTI "Set to ON to build FlightGear with RTI support" OFF) option(ENABLE_PROFILE "Set to ON to build FlightGear with gperftools profiling support" OFF) -option(JPEG_FACTORY "Set to ON to build FlightGear with JPEG-factory support" OFF) option(SYSTEM_SQLITE "Set to ON to build FlightGear with the system's SQLite3 library" OFF) option(ENABLE_IAX "Set to ON to build FlightGear with IAXClient/fgcom built-in (default)" ON) option(USE_DBUS "Set to ON to build FlightGear with DBus screensaver interaction (default on Linux)" ${USE_DBUS_DEFAULT}) @@ -293,26 +292,6 @@ find_package(SimGear ${FLIGHTGEAR_VERSION} REQUIRED) ############################################################################## -if (JPEG_FACTORY) - # check simgear was built with JPEG-factory support - find_package(JPEG REQUIRED) - include_directories(${JPEG_INCLUDE_DIR}) - - set(CMAKE_REQUIRED_INCLUDES - ${SIMGEAR_INCLUDE_DIR} - ${JPEG_INCLUDE_DIR} - ${OPENSCENEGRAPH_INCLUDE_DIRS}) - - check_cxx_source_compiles( - "#include - int main() { return 0; } " - FG_JPEG_SERVER) - - if (NOT FG_JPEG_SERVER) - message(STATUS "JPEG server support requested, but SimGear was built without JPEG support") - endif() -endif() - check_include_file(unistd.h HAVE_UNISTD_H) check_include_file(sys/time.h HAVE_SYS_TIME_H) check_include_file(windows.h HAVE_WINDOWS_H) diff --git a/src/GUI/gui_funcs.cxx b/src/GUI/gui_funcs.cxx index 8aff7f465..8eeb54e76 100644 --- a/src/GUI/gui_funcs.cxx +++ b/src/GUI/gui_funcs.cxx @@ -548,79 +548,16 @@ namespace SGPath _path; }; -} +} // of anonymous namespace osg::ref_ptr GUISnapShotOperation::_snapShotOp; // do a screen snap shot bool fgDumpSnapShot () { -#if 1 // start snap shot operation, while needs to be executed in // graphics context return GUISnapShotOperation::start(); -#else - // obsolete code => remove when new code is stable - SGPropertyNode_ptr master_freeze = fgGetNode("/sim/freeze/master"); - - bool freeze = master_freeze->getBoolValue(); - if ( !freeze ) { - master_freeze->setBoolValue(true); - } - - int mouse = fgGetMouseCursor(); - fgSetMouseCursor(MOUSE_CURSOR_NONE); - - fgSetBool("/sim/signals/screenshot", true); - - FGRenderer *renderer = globals->get_renderer(); - renderer->resize( fgGetInt("/sim/startup/xsize"), - fgGetInt("/sim/startup/ysize") ); - - // we need two render frames here to clear the menu and cursor - // ... not sure why but doing an extra fgRenderFrame() shouldn't - // hurt anything - renderer->update( true ); - renderer->update( true ); - - string dir = fgGetString("/sim/paths/screenshot-dir"); - if (dir.empty()) - dir = fgGetString("/sim/fg-current"); - - SGPath path(dir + '/'); - if (path.create_dir( 0755 )) { - SG_LOG(SG_GENERAL, SG_ALERT, "Cannot create screenshot directory '" - << dir << "'. Trying home directory."); - dir = globals->get_fg_home(); - } - - char filename[24]; - static int count = 1; - while (count < 1000) { - snprintf(filename, 24, "fgfs-screen-%03d.png", count++); - - SGPath p(dir); - p.append(filename); - if (!p.exists()) { - path.set(p.str()); - break; - } - } - - bool result = sg_glDumpWindow(path.c_str(), - fgGetInt("/sim/startup/xsize"), - fgGetInt("/sim/startup/ysize")); - - fgSetString("/sim/paths/screenshot-last", path.c_str()); - fgSetBool("/sim/signals/screenshot", false); - - fgSetMouseCursor(mouse); - - if ( !freeze ) { - master_freeze->setBoolValue(false); - } - return result; -#endif } // do an entire scenegraph dump diff --git a/src/Include/config_cmake.h.in b/src/Include/config_cmake.h.in index 3c8295f65..1faa29f44 100644 --- a/src/Include/config_cmake.h.in +++ b/src/Include/config_cmake.h.in @@ -35,7 +35,6 @@ // Ensure FG_HAVE_xxx always have a value #define FG_HAVE_HLA (@FG_HAVE_HLA@ + 0) #define FG_HAVE_GPERFTOOLS (@FG_HAVE_GPERFTOOLS@ + 0) -#cmakedefine FG_JPEG_SERVER #cmakedefine SYSTEM_SQLITE diff --git a/src/Main/fg_io.cxx b/src/Main/fg_io.cxx index 45d6a1e61..059bb97a2 100644 --- a/src/Main/fg_io.cxx +++ b/src/Main/fg_io.cxx @@ -50,9 +50,7 @@ #include #include #include -#ifdef FG_JPEG_SERVER -# include -#endif +#include #include #include #include @@ -155,12 +153,21 @@ FGIO::parse_port_config( const string& config ) // determine port string port = tokens[1]; return new FGHttpd( atoi(port.c_str()) ); -#ifdef FG_JPEG_SERVER } else if ( protocol == "jpg-httpd" ) { // determine port - string port = tokens[1]; - return new FGJpegHttpd( atoi(port.c_str()) ); -#endif + int port = simgear::strutils::to_int(tokens[1]); + int frameHz = 8; // maximum frame rate + string type = "jpeg"; + + if (tokens.size() > 2) { + frameHz = simgear::strutils::to_int(tokens[2]); + } + + if (tokens.size() > 3) { + type = tokens[3]; + } + + return new FGJpegHttpd(port, frameHz, type); } else if ( protocol == "joyclient" ) { FGJoyClient *joyclient = new FGJoyClient; io = joyclient; diff --git a/src/Main/options.cxx b/src/Main/options.cxx index 241e77404..70477ad2c 100644 --- a/src/Main/options.cxx +++ b/src/Main/options.cxx @@ -1558,9 +1558,7 @@ struct OptionDesc { {"atcsim", true, OPTION_CHANNEL, "", false, "dummy", 0 }, {"atlas", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 }, {"httpd", true, OPTION_CHANNEL, "", false, "", 0 }, -#ifdef FG_JPEG_SERVER {"jpg-httpd", true, OPTION_CHANNEL, "", false, "", 0 }, -#endif {"native", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 }, {"native-ctrls", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 }, {"native-fdm", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 }, diff --git a/src/Network/CMakeLists.txt b/src/Network/CMakeLists.txt index b4cc36ec8..56c2551f8 100644 --- a/src/Network/CMakeLists.txt +++ b/src/Network/CMakeLists.txt @@ -27,6 +27,7 @@ set(SOURCES pve.cxx ray.cxx rul.cxx + jpg-httpd.cxx ) set(HEADERS @@ -56,17 +57,13 @@ set(HEADERS pve.hxx ray.hxx rul.hxx + jpg-httpd.hxx ) if(ENABLE_IAX) list(APPEND SOURCES fgcom.cxx) list(APPEND HEADERS fgcom.hxx) endif() - -if(FG_JPEG_SERVER) - list(APPEND SOURCES jpg-httpd.cxx) - list(APPEND HEADERS jpg-httpd.hxx) -endif() flightgear_component(Network "${SOURCES}" "${HEADERS}") 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(); } + diff --git a/src/Network/jpg-httpd.hxx b/src/Network/jpg-httpd.hxx index ce6e78acb..95568c054 100644 --- a/src/Network/jpg-httpd.hxx +++ b/src/Network/jpg-httpd.hxx @@ -26,17 +26,20 @@ #ifndef _FG_JPEG_HTTPD_HXX #define _FG_JPEG_HTTPD_HXX +#include // for auto_ptr +#include + #include "protocol.hxx" -class HttpdImageServer; +// forward decls +class HttpdThread; class FGJpegHttpd : public FGProtocol { - int port; - HttpdImageServer *imageServer; + std::auto_ptr _imageServer; public: - FGJpegHttpd( int p ); + FGJpegHttpd( int p, int hz, const std::string& type ); ~FGJpegHttpd(); bool open(); diff --git a/src/Viewer/renderer.cxx b/src/Viewer/renderer.cxx index 098ffe960..52e84bfc1 100644 --- a/src/Viewer/renderer.cxx +++ b/src/Viewer/renderer.cxx @@ -86,9 +86,6 @@ #include #include #include -#ifdef FG_JPEG_SERVER -#include -#endif #include