]> git.mxchange.org Git - flightgear.git/blobdiff - src/Network/jpg-httpd.cxx
httpd: don't spam the console with debug messages
[flightgear.git] / src / Network / jpg-httpd.cxx
index 0c84f5b7d93ccaa863c99c7ee96ca377fe5856c0..b3171827cc75d3c319aeb3afa4914550abbfdf2c 100644 (file)
 #include <simgear/compiler.h>
 
 #include <cstdlib>        // atoi() atof()
-
 #include <cstring>
 
+#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/screen/jpgfactory.hxx>
+#include <simgear/threads/SGThread.hxx>
+#include <simgear/threads/SGGuard.hxx>
 
 #include <Main/fg_props.hxx>
 #include <Main/globals.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;
 
-/* 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<osg::Image> _image;
+    std::string _compressedData;
 };
 
-//////////////////////////////////////////////////////////////
-// class HttpdImageServer
+typedef SGSharedPtr<CompressedImageBuffer> 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<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;
+};
 
-    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<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;
+};
 
-        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<CompressedImageBuffer> 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<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 );
+}
+
 //////////////////////////////////////////////////////////////
 // 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();
 }
+