1 // jpg-httpd.cxx -- FGFS jpg-http interface
3 // Written by Curtis Olson, started June 2001.
5 // Copyright (C) 2001 Curtis L. Olson - http://www.flightgear.org/~curt
7 // Jpeg Image Support added August 2001
8 // by Norman Vine - nhv@cape.com
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.
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.
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.
31 #include <simgear/compiler.h>
33 #include <cstdlib> // atoi() atof()
36 #include <osgDB/Registry>
37 #include <osgDB/ReaderWriter>
38 #include <osgUtil/SceneView>
39 #include <osgViewer/Viewer>
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>
49 #include <Main/fg_props.hxx>
50 #include <Main/globals.hxx>
51 #include <Viewer/renderer.hxx>
53 #include "jpg-httpd.hxx"
55 #define HTTP_GET_STRING "GET "
56 #define HTTP_TERMINATOR "\r\n"
60 //////////////////////////////////////////////////////////////
61 // class CompressedImageBuffer
62 //////////////////////////////////////////////////////////////
64 class CompressedImageBuffer : public SGReferenced
67 CompressedImageBuffer(osg::Image* img) :
72 bool compress(const std::string& extension)
74 osgDB::ReaderWriter* writer =
75 osgDB::Registry::instance()->getReaderWriterForExtension(extension);
77 std::stringstream outputStream;
78 osgDB::ReaderWriter::WriteResult wr;
79 wr = writer->writeImage(*_image,outputStream, NULL);
82 _compressedData = outputStream.str();
91 return _compressedData.length();
94 const char* data() const
96 return _compressedData.data();
99 osg::ref_ptr<osg::Image> _image;
100 std::string _compressedData;
103 typedef SGSharedPtr<CompressedImageBuffer> ImageBufferPtr;
105 //////////////////////////////////////////////////////////////
107 class HttpdThread : public SGThread, private simgear::NetChannel
110 HttpdThread (int port, int frameHz, const std::string& type) :
123 void setNewFrameImage(osg::Image* frameImage)
125 SGGuard<SGMutex> g(_newFrameLock);
126 _newFrame = frameImage;
129 int getFrameHz() const
137 virtual bool writable (void) { return false; }
139 virtual void handleAccept (void);
142 simgear::NetChannelPoller _poller;
144 std::string _imageType;
146 SGMutex _newFrameLock;
147 osg::ref_ptr<osg::Image> _newFrame;
148 /// current frame we're serving to new connections
149 ImageBufferPtr _currentFrame;
153 ///////////////////////////////////////////////////////////////////////////
155 class WindowCaptureCallback : public osg::Camera::DrawCallback
173 struct ContextData : public osg::Referenced
175 ContextData(osg::GraphicsContext* gc, Mode mode, GLenum readBuffer, HttpdThread* httpd):
178 _readBuffer(readBuffer),
179 _pixelFormat(GL_RGB),
180 _type(GL_UNSIGNED_BYTE),
183 _currentImageIndex(0),
186 _previousFrameTick = osg::Timer::instance()->tick();
188 if (gc->getTraits() && gc->getTraits()->alpha) {
189 _pixelFormat = GL_RGBA;
192 getSize(gc, _width, _height);
194 // single buffered image
195 _imageBuffer.push_back(new osg::Image);
198 void getSize(osg::GraphicsContext* gc, int& width, int& height)
202 width = gc->getTraits()->width;
203 height = gc->getTraits()->height;
209 typedef std::vector< osg::ref_ptr<osg::Image> > ImageBuffer;
211 osg::GraphicsContext* _gc;
218 unsigned int _currentImageIndex;
219 ImageBuffer _imageBuffer;
220 osg::Timer_t _previousFrameTick;
224 WindowCaptureCallback(HttpdThread *thread, Mode mode, FramePosition position, GLenum readBuffer):
227 _readBuffer(readBuffer),
232 FramePosition getFramePosition() const { return _position; }
234 ContextData* createContextData(osg::GraphicsContext* gc) const
236 return new ContextData(gc, _mode, _readBuffer, _thread);
239 ContextData* getContextData(osg::GraphicsContext* gc) const
241 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
242 osg::ref_ptr<ContextData>& data = _contextDataMap[gc];
243 if (!data) data = createContextData(gc);
248 virtual void operator () (osg::RenderInfo& renderInfo) const
250 glReadBuffer(_readBuffer);
252 osg::GraphicsContext* gc = renderInfo.getState()->getGraphicsContext();
253 osg::ref_ptr<ContextData> cd = getContextData(gc);
257 typedef std::map<osg::GraphicsContext*, osg::ref_ptr<ContextData> > ContextDataMap;
260 FramePosition _position;
262 mutable OpenThreads::Mutex _mutex;
263 mutable ContextDataMap _contextDataMap;
264 HttpdThread* _thread;
267 void WindowCaptureCallback::ContextData::readPixels()
269 osg::Timer_t n = osg::Timer::instance()->tick();
270 double dt = osg::Timer::instance()->delta_s(_previousFrameTick,n);
271 double frameInterval = 1.0 / _httpd->getFrameHz();
272 if (dt < frameInterval)
275 _previousFrameTick = n;
276 unsigned int nextImageIndex = (_currentImageIndex+1)%_imageBuffer.size();
278 int width=0, height=0;
279 getSize(_gc, width, height);
280 if (width!=_width || _height!=height)
286 osg::Image* image = _imageBuffer[_currentImageIndex].get();
287 image->readPixels(0,0,_width,_height,
290 _httpd->setNewFrameImage(image);
291 _currentImageIndex = nextImageIndex;
294 osg::Camera* findLastCamera(osgViewer::ViewerBase& viewer)
296 osgViewer::ViewerBase::Windows windows;
297 viewer.getWindows(windows);
298 for(osgViewer::ViewerBase::Windows::iterator itr = windows.begin();
299 itr != windows.end();
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();
311 if ((*cam_itr)->getRenderOrder() > lastCamera->getRenderOrder())
313 lastCamera = (*cam_itr);
315 if ((*cam_itr)->getRenderOrder() == lastCamera->getRenderOrder() &&
316 (*cam_itr)->getRenderOrderNum() >= lastCamera->getRenderOrderNum())
318 lastCamera = (*cam_itr);
323 lastCamera = *cam_itr;
333 //////////////////////////////////////////////////////////////
334 // class HttpdImageChannel
335 //////////////////////////////////////////////////////////////
337 class HttpdImageChannel : public simgear::NetChannel
340 HttpdImageChannel(SGSharedPtr<CompressedImageBuffer> img) :
346 void setMimeType(const std::string& s)
351 virtual void collectIncomingData (const char* s, int n)
353 _request += string(s, n);
356 virtual void handleRead()
359 int num_read = recv(data, 512);
361 _request += std::string(data, num_read);
364 if (_request.find(HTTP_TERMINATOR) != std::string::npos) {
365 // have complete first line of request
366 if (_request.find(HTTP_GET_STRING) == 0) {
372 virtual void handleWrite()
378 void processRequest();
381 std::string _request;
382 ImageBufferPtr _imageData;
383 std::string _mimeType;
387 //////////////////////////////////////////////////////////////
389 //////////////////////////////////////////////////////////////
391 bool HttpdThread::init()
394 SG_LOG( SG_IO, SG_ALERT, "Failed to open HttpdImage port.");
398 if (0 != bind( "", _port )) {
399 SG_LOG( SG_IO, SG_ALERT, "Failed to bind HttpdImage port.");
403 if (0 != listen( 5 )) {
404 SG_LOG( SG_IO, SG_ALERT, "Failed to listen on HttpdImage port.");
408 _poller.addChannel(this);
410 osgViewer::Viewer* v = globals->get_renderer()->getViewer();
411 osg::Camera* c = findLastCamera(*v);
416 c->setFinalDrawCallback(new WindowCaptureCallback(this,
417 WindowCaptureCallback::READ_PIXELS,
418 WindowCaptureCallback::END_FRAME,
422 SG_LOG(SG_IO, SG_INFO, "HttpdImage server started on port " << _port);
426 void HttpdThread::shutdown()
431 osgViewer::Viewer* v = globals->get_renderer()->getViewer();
432 osg::Camera* c = findLastCamera(*v);
433 c->setFinalDrawCallback(NULL);
435 SG_LOG(SG_IO, SG_INFO, "HttpdImage server shutdown on port " << _port);
438 void HttpdThread::run()
441 _poller.poll(10); // 10msec sleep
443 // locked section to check for a new raw frame from the callback
444 osg::ref_ptr<osg::Image> frameToWriteOut;
446 SGGuard<SGMutex> g(_newFrameLock);
448 frameToWriteOut = _newFrame;
451 } // of locked section
453 // no locking needed on currentFrame; channels run in this thread
454 if (frameToWriteOut) {
455 _currentFrame = ImageBufferPtr(new CompressedImageBuffer(frameToWriteOut));
456 _currentFrame->compress(_imageType);
458 } // of thread poll loop
461 void HttpdThread::handleAccept (void)
463 simgear::IPAddress addr;
464 int handle = accept ( &addr );
465 SG_LOG( SG_IO, SG_DEBUG, "Client " << addr.getHost() << ":" << addr.getPort() << " connected" );
467 HttpdImageChannel *hc = new HttpdImageChannel(_currentFrame);
468 hc->setMimeType("image/" + _imageType);
469 hc->setHandle ( handle );
471 _poller.addChannel( hc );
474 //////////////////////////////////////////////////////////////
476 //////////////////////////////////////////////////////////////
478 FGJpegHttpd::FGJpegHttpd( int p, int hz, const std::string& type )
480 _imageServer.reset(new HttpdThread( p, hz, type ));
483 FGJpegHttpd::~FGJpegHttpd()
487 bool FGJpegHttpd::open()
489 if ( is_enabled() ) {
490 SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
491 << "is already in use, ignoring" );
495 if (!_imageServer->init()) {
496 SG_LOG( SG_IO, SG_ALERT, "FGJpegHttpd: failed to init Http sevrer thread");
500 _imageServer->start();
506 bool FGJpegHttpd::process()
511 bool FGJpegHttpd::close()
513 _imageServer->shutdown();
517 void HttpdImageChannel::processRequest()
519 std::ostringstream ss;
520 _bytesToSend = _imageData->size();
521 if (_bytesToSend <= 0) {
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
533 if( getHandle() == -1 )
535 SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Invalid socket handle. Ignoring request.\n" );
540 string headersData(ss.str());
541 if (send(headersData.c_str(), headersData.length()) <= 0 )
543 SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Error to send HTTP response. Ignoring request.\n" );
547 _bytesToSend = _imageData->size();
551 void HttpdImageChannel::sendResponse()
553 const char* ptr = _imageData->data();
555 ptr += (_imageData->size() - _bytesToSend);
556 size_t sent = send(ptr, _bytesToSend);
558 SG_LOG( SG_IO, SG_DEBUG, "<<<<<<<<< Error to send HTTP response. Ignoring request.\n" );
562 _bytesToSend -= sent;
563 if (_bytesToSend == 0) {
565 shouldDelete(); // use NetChannelPoller delete mechanism