]> git.mxchange.org Git - flightgear.git/blob - src/Network/jpg-httpd.cxx
Update image-server logic.
[flightgear.git] / src / Network / jpg-httpd.cxx
1 // jpg-httpd.cxx -- FGFS jpg-http interface
2 //
3 // Written by Curtis Olson, started June 2001.
4 //
5 // Copyright (C) 2001  Curtis L. Olson - http://www.flightgear.org/~curt
6 //
7 // Jpeg Image Support added August 2001
8 //  by Norman Vine - nhv@cape.com
9 //
10 // This program is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU General Public License as
12 // published by the Free Software Foundation; either version 2 of the
13 // License, or (at your option) any later version.
14 //
15 // This program is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 // General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
23 //
24 // $Id$
25
26
27 #ifdef HAVE_CONFIG_H
28 #  include <config.h>
29 #endif
30
31 #include <simgear/compiler.h>
32
33 #include <cstdlib>        // atoi() atof()
34 #include <cstring>
35
36 #include <osgDB/Registry>
37 #include <osgDB/ReaderWriter>
38 #include <osgUtil/SceneView>
39 #include <osgViewer/Viewer>
40
41 #include <simgear/debug/logstream.hxx>
42 #include <simgear/io/iochannel.hxx>
43 #include <simgear/math/sg_types.hxx>
44 #include <simgear/props/props.hxx>
45 #include <simgear/io/sg_netChat.hxx>
46 #include <simgear/threads/SGThread.hxx>
47 #include <simgear/threads/SGGuard.hxx>
48
49 #include <Main/fg_props.hxx>
50 #include <Main/globals.hxx>
51 #include <Viewer/renderer.hxx>
52
53 #include "jpg-httpd.hxx"
54
55 #define HTTP_GET_STRING           "GET "
56 #define HTTP_TERMINATOR             "\r\n"
57
58 using std::string;
59
60 //////////////////////////////////////////////////////////////
61 // class CompressedImageBuffer
62 //////////////////////////////////////////////////////////////
63
64 class CompressedImageBuffer : public SGReferenced
65 {
66 public:
67     CompressedImageBuffer(osg::Image* img) :
68         _image(img)
69     {
70     }
71     
72     bool compress(const std::string& extension)
73     {
74         osgDB::ReaderWriter* writer =
75             osgDB::Registry::instance()->getReaderWriterForExtension(extension);
76         
77         std::stringstream outputStream;
78         osgDB::ReaderWriter::WriteResult wr;
79         wr = writer->writeImage(*_image,outputStream, NULL);
80         
81         if(wr.success()) {
82             _compressedData = outputStream.str();
83             return true;
84         } else {
85             return false;
86         }
87     }
88     
89     size_t size() const
90     {
91         return _compressedData.length();
92     }
93     
94     const char* data() const
95     {
96         return _compressedData.data();
97     }
98 private:
99     osg::ref_ptr<osg::Image> _image;
100     std::string _compressedData;
101 };
102
103 typedef SGSharedPtr<CompressedImageBuffer> ImageBufferPtr;
104
105 //////////////////////////////////////////////////////////////
106
107 class HttpdThread : public SGThread, private simgear::NetChannel
108 {
109 public:
110     HttpdThread (int port, int frameHz, const std::string& type) :
111         _done(false),
112         _port(port),
113         _hz(frameHz),
114         _imageType(type)
115     {
116     }
117     
118     bool init();
119     void shutdown();
120     
121     virtual void run();
122     
123     void setNewFrameImage(osg::Image* frameImage)
124     {
125         SGGuard<SGMutex> g(_newFrameLock);
126         _newFrame = frameImage;
127     }
128     
129     int getFrameHz() const
130     { return _hz; }
131     
132     void setDone()
133     {
134         _done = true;
135     }
136 private:
137     virtual bool writable (void) { return false; }
138     
139     virtual void handleAccept (void);
140     
141     bool _done;
142     simgear::NetChannelPoller _poller;
143     int _port, _hz;
144     std::string _imageType;
145     
146     SGMutex _newFrameLock;
147     osg::ref_ptr<osg::Image> _newFrame;
148     /// current frame we're serving to new connections
149     ImageBufferPtr _currentFrame;
150 };
151
152
153 ///////////////////////////////////////////////////////////////////////////
154
155 class WindowCaptureCallback : public osg::Camera::DrawCallback
156 {
157 public:
158     
159     enum Mode
160     {
161         READ_PIXELS,
162         SINGLE_PBO,
163         DOUBLE_PBO,
164         TRIPLE_PBO
165     };
166     
167     enum FramePosition
168     {
169         START_FRAME,
170         END_FRAME
171     };
172     
173     struct ContextData : public osg::Referenced
174     {
175         ContextData(osg::GraphicsContext* gc, Mode mode, GLenum readBuffer, HttpdThread* httpd):
176             _gc(gc),
177             _mode(mode),
178             _readBuffer(readBuffer),
179             _pixelFormat(GL_RGB),
180             _type(GL_UNSIGNED_BYTE),
181             _width(0),
182             _height(0),
183             _currentImageIndex(0),
184             _httpd(httpd)
185         {
186             _previousFrameTick = osg::Timer::instance()->tick();
187             
188             if (gc->getTraits() && gc->getTraits()->alpha) {
189                 _pixelFormat = GL_RGBA;
190             }
191             
192             getSize(gc, _width, _height);
193             
194             // single buffered image
195             _imageBuffer.push_back(new osg::Image);
196         }
197         
198         void getSize(osg::GraphicsContext* gc, int& width, int& height)
199         {
200             if (gc->getTraits())
201             {
202                 width = gc->getTraits()->width;
203                 height = gc->getTraits()->height;
204             }
205         }
206         
207         void readPixels();
208         
209         typedef std::vector< osg::ref_ptr<osg::Image> >  ImageBuffer;
210         
211         osg::GraphicsContext*   _gc;
212         Mode                    _mode;
213         GLenum                  _readBuffer;
214         GLenum                  _pixelFormat;
215         GLenum                  _type;
216         int                     _width;
217         int                     _height;
218         unsigned int            _currentImageIndex;
219         ImageBuffer             _imageBuffer;
220         osg::Timer_t            _previousFrameTick;
221         HttpdThread*            _httpd;
222     };
223     
224     WindowCaptureCallback(HttpdThread *thread, Mode mode, FramePosition position, GLenum readBuffer):
225         _mode(mode),
226         _position(position),
227         _readBuffer(readBuffer),
228         _thread(thread)
229     {
230     }
231     
232     FramePosition getFramePosition() const { return _position; }
233     
234     ContextData* createContextData(osg::GraphicsContext* gc) const
235     {
236         return new ContextData(gc, _mode, _readBuffer, _thread);
237     }
238     
239     ContextData* getContextData(osg::GraphicsContext* gc) const
240     {
241         OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
242         osg::ref_ptr<ContextData>& data = _contextDataMap[gc];
243         if (!data) data = createContextData(gc);
244         
245         return data.get();
246     }
247     
248     virtual void operator () (osg::RenderInfo& renderInfo) const
249     {
250         glReadBuffer(_readBuffer);
251         
252         osg::GraphicsContext* gc = renderInfo.getState()->getGraphicsContext();
253         osg::ref_ptr<ContextData> cd = getContextData(gc);
254         cd->readPixels();
255     }
256     
257     typedef std::map<osg::GraphicsContext*, osg::ref_ptr<ContextData> > ContextDataMap;
258     
259     Mode                        _mode;
260     FramePosition               _position;
261     GLenum                      _readBuffer;
262     mutable OpenThreads::Mutex  _mutex;
263     mutable ContextDataMap      _contextDataMap;
264     HttpdThread*                _thread;
265 };
266
267 void WindowCaptureCallback::ContextData::readPixels()
268 {
269     osg::Timer_t n = osg::Timer::instance()->tick();
270     double dt = osg::Timer::instance()->delta_s(n, _previousFrameTick);
271     double frameInterval = 1.0 / _httpd->getFrameHz();
272     if (dt < frameInterval)
273         return;
274     
275     _previousFrameTick = n;
276     unsigned int nextImageIndex = (_currentImageIndex+1)%_imageBuffer.size();
277     
278     int width=0, height=0;
279     getSize(_gc, width, height);
280     if (width!=_width || _height!=height)
281     {
282         _width = width;
283         _height = height;
284     }
285     
286     osg::Image* image = _imageBuffer[_currentImageIndex].get();
287     image->readPixels(0,0,_width,_height,
288                       _pixelFormat,_type);
289     
290     _httpd->setNewFrameImage(image);
291     _currentImageIndex = nextImageIndex;
292 }
293
294 osg::Camera* findLastCamera(osgViewer::ViewerBase& viewer)
295 {
296     osgViewer::ViewerBase::Windows windows;
297     viewer.getWindows(windows);
298     for(osgViewer::ViewerBase::Windows::iterator itr = windows.begin();
299         itr != windows.end();
300         ++itr)
301     {
302         osgViewer::GraphicsWindow* window = *itr;
303         osg::GraphicsContext::Cameras& cameras = window->getCameras();
304         osg::Camera* lastCamera = 0;
305         for(osg::GraphicsContext::Cameras::iterator cam_itr = cameras.begin();
306             cam_itr != cameras.end();
307             ++cam_itr)
308         {
309             if (lastCamera)
310             {
311                 if ((*cam_itr)->getRenderOrder() > lastCamera->getRenderOrder())
312                 {
313                     lastCamera = (*cam_itr);
314                 }
315                 if ((*cam_itr)->getRenderOrder() == lastCamera->getRenderOrder() &&
316                     (*cam_itr)->getRenderOrderNum() >= lastCamera->getRenderOrderNum())
317                 {
318                     lastCamera = (*cam_itr);
319                 }
320             }
321             else
322             {
323                 lastCamera = *cam_itr;
324             }
325         }
326         
327         return lastCamera;
328     }
329     
330     return NULL;
331 }
332
333 //////////////////////////////////////////////////////////////
334 // class HttpdImageChannel
335 //////////////////////////////////////////////////////////////
336
337 class HttpdImageChannel : public simgear::NetChannel
338 {
339 public:
340     HttpdImageChannel(SGSharedPtr<CompressedImageBuffer> img) :
341         _imageData(img),
342         _bytesToSend(0)
343     {
344     }
345
346     void setMimeType(const std::string& s)
347     {
348         _mimeType = s;
349     }
350
351     virtual void collectIncomingData (const char* s, int n)
352     {
353         _request += string(s, n);
354     }
355     
356     virtual void handleRead()
357     {
358         char data[512];
359         int num_read = recv(data, 512);
360         if (num_read > 0) {
361             _request += std::string(data, num_read);
362         }
363         
364         if (_request.find(HTTP_TERMINATOR) != std::string::npos) {
365             // have complete first line of request
366             if (_request.find(HTTP_GET_STRING) == 0) {
367                 processRequest();
368             }
369         }
370     }
371
372     virtual void handleWrite()
373     {
374         sendResponse();
375     }
376     
377 private:
378     void processRequest();
379     void sendResponse();
380
381     std::string _request;
382     ImageBufferPtr _imageData;
383     std::string _mimeType;
384     size_t _bytesToSend;
385 };
386
387 //////////////////////////////////////////////////////////////
388 // class HttpdThread
389 //////////////////////////////////////////////////////////////
390
391 bool HttpdThread::init()
392 {
393     if (!open()) {
394         SG_LOG( SG_IO, SG_ALERT, "Failed to open HttpdImage port.");
395         return false;
396     }
397     
398     if (0 != bind( "", _port )) {
399         SG_LOG( SG_IO, SG_ALERT, "Failed to bind HttpdImage port.");
400         return false;
401     }
402     
403     if (0 != listen( 5 )) {
404         SG_LOG( SG_IO, SG_ALERT, "Failed to listen on HttpdImage port.");
405         return false;
406     }
407     
408     _poller.addChannel(this);
409     
410     osgViewer::Viewer* v = globals->get_renderer()->getViewer();
411     osg::Camera* c = findLastCamera(*v);
412     if (!c) {
413         return false;
414     }
415     
416     c->setFinalDrawCallback(new WindowCaptureCallback(this,
417                                                       WindowCaptureCallback::READ_PIXELS,
418                                                       WindowCaptureCallback::END_FRAME,
419                                                       GL_BACK));
420
421     
422     SG_LOG(SG_IO, SG_INFO, "HttpdImage server started on port " << _port);
423     return true;
424 }
425
426 void HttpdThread::shutdown()
427 {
428     setDone();
429     join();
430     
431     osgViewer::Viewer* v = globals->get_renderer()->getViewer();
432     osg::Camera* c = findLastCamera(*v);
433     c->setFinalDrawCallback(NULL);
434     
435     SG_LOG(SG_IO, SG_INFO, "HttpdImage server shutdown on port " << _port);
436 }
437
438 void HttpdThread::run()
439 {
440     while (!_done) {
441         _poller.poll(10); // 10msec sleep
442         
443         // locked section to check for a new raw frame from the callback
444         osg::ref_ptr<osg::Image> frameToWriteOut;
445         {
446             SGGuard<SGMutex> g(_newFrameLock);
447             if (_newFrame) {
448                 frameToWriteOut = _newFrame;
449                 _newFrame = NULL;
450             }
451         } // of locked section
452         
453         // no locking needed on currentFrame; channels run in this thread
454         if (frameToWriteOut) {
455             _currentFrame = ImageBufferPtr(new CompressedImageBuffer(frameToWriteOut));
456             _currentFrame->compress(_imageType);
457         }
458     } // of thread poll loop
459 }
460     
461 void HttpdThread::handleAccept (void)
462 {
463     simgear::IPAddress addr;
464     int handle = accept ( &addr );
465     SG_LOG( SG_IO, SG_DEBUG, "Client " << addr.getHost() << ":" << addr.getPort() << " connected" );
466     
467     HttpdImageChannel *hc = new HttpdImageChannel(_currentFrame);
468     hc->setMimeType("image/" + _imageType);
469     hc->setHandle ( handle );
470     
471     _poller.addChannel( hc );
472 }
473
474 //////////////////////////////////////////////////////////////
475 // class FGJpegHttpd
476 //////////////////////////////////////////////////////////////
477
478 FGJpegHttpd::FGJpegHttpd( int p, int hz, const std::string& type )
479 {
480     _imageServer.reset(new HttpdThread( p, hz, type ));
481 }
482
483 FGJpegHttpd::~FGJpegHttpd()
484 {
485 }
486
487 bool FGJpegHttpd::open()
488 {
489     if ( is_enabled() ) {
490         SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
491         << "is already in use, ignoring" );
492         return false;
493     }
494
495     if (!_imageServer->init()) {
496         SG_LOG( SG_IO, SG_ALERT, "FGJpegHttpd: failed to init Http sevrer thread");
497         return false;
498     }
499     
500     _imageServer->start();
501     set_enabled( true );
502
503     return true;
504 }
505
506 bool FGJpegHttpd::process()
507 {
508     return true;
509 }
510
511 bool FGJpegHttpd::close()
512 {
513     _imageServer->shutdown();
514     return true;
515 }
516
517 void HttpdImageChannel::processRequest()
518 {
519     std::ostringstream ss;
520     _bytesToSend = _imageData->size();
521     if (_bytesToSend <= 0) {
522         return;
523     }
524
525     // assemble HTTP 1.1 headers. Connection: close is important since we
526     // don't attempt pipelining at all
527     ss << "HTTP/1.1 200 OK" << HTTP_TERMINATOR;
528     ss << "Content-Type: " << _mimeType << HTTP_TERMINATOR;
529     ss << "Content-Length: " << _bytesToSend << HTTP_TERMINATOR;
530     ss << "Connection: close" << HTTP_TERMINATOR;
531     ss << HTTP_TERMINATOR; // end of headers
532     
533     if( getHandle() == -1 )
534     {
535         SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Invalid socket handle. Ignoring request.\n" );
536         return;
537     }
538     
539     // send headers out
540     string headersData(ss.str());
541     if (send(headersData.c_str(), headersData.length()) <= 0 )
542     {
543         SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Error to send HTTP response. Ignoring request.\n" );
544         return;
545     }
546     
547     _bytesToSend = _imageData->size();
548     sendResponse();
549 }
550
551 void HttpdImageChannel::sendResponse()
552 {
553     const char* ptr = _imageData->data();
554
555     ptr += (_imageData->size() - _bytesToSend);
556     size_t sent = send(ptr, _bytesToSend);
557     if (sent <= 0) {
558         SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Error to send HTTP response. Ignoring request.\n" );
559         return;
560     }
561     
562     _bytesToSend -= sent;
563     if (_bytesToSend == 0) {
564         close();
565         shouldDelete(); // use NetChannelPoller delete mechanism
566     }
567 }
568