diff --git a/AsyncQueue.h b/AsyncQueue.h index 7c6e1af..56167c7 100644 --- a/AsyncQueue.h +++ b/AsyncQueue.h @@ -30,6 +30,9 @@ * used in classical producers/consumers multithreaded scenario. The * template parameter is the class which can be put/get to/from the * queue. + * + * We could probably use GAsyncQueue instead. + * See also https://github.com/aalex/toonloop/blob/master/src/concurrentqueue.h */ template class AsyncQueue @@ -43,6 +46,10 @@ public: return this->buffer.size(); } + /** + * Appends an element to the queue. + * Does not manage ref/unref copy/free memory. + */ void put(const T& item) { QMutexLocker locker(&mutex); diff --git a/MM.cpp b/MM.cpp index 97098ec..5dbd57f 100644 --- a/MM.cpp +++ b/MM.cpp @@ -19,7 +19,11 @@ #include "MM.h" +const QString MM::APPLICATION_NAME = "MapMap"; const QString MM::VERSION = "0.1.1"; +const QString MM::COPYRIGHT_OWNERS = "Sofian Audry, Alexandre Quessy, Mike Latona, Vasilis Liaskovitis, Dame Diongue"; +const QString MM::ORGANIZATION_NAME = "MapMap"; +const QString MM::ORGANIZATION_DOMAIN = "mapmap.info"; const QString MM::FILE_EXTENSION = "mmp"; const QString MM::VIDEO_FILES_FILTER = "*.mov *.mp4 *.avi *.ogg *.ogv *.mpeg *.mpeg1 *.mpeg4 *.mpg *.mpg2 *.mp2 *.mjpq *.mjp *.wmv *sock"; const QString MM::IMAGE_FILES_FILTER = "*.jpg *.jpeg *.gif *.png *.tiff *.tif *.bmp"; diff --git a/MM.h b/MM.h index 22fc5aa..fade564 100644 --- a/MM.h +++ b/MM.h @@ -29,7 +29,11 @@ class MM { public: // General. + static const QString APPLICATION_NAME; static const QString VERSION; + static const QString COPYRIGHT_OWNERS; + static const QString ORGANIZATION_NAME; + static const QString ORGANIZATION_DOMAIN; static const QString FILE_EXTENSION; static const QString VIDEO_FILES_FILTER; static const QString IMAGE_FILES_FILTER; diff --git a/MainApplication.cpp b/MainApplication.cpp index 3e97e79..ed17f8c 100644 --- a/MainApplication.cpp +++ b/MainApplication.cpp @@ -23,11 +23,20 @@ MainApplication::MainApplication(int &argc, char *argv[]) : QApplication(argc, argv) { + // Initialize GStreamer. + gst_init (NULL, NULL); + // Set application information. + setApplicationName(MM::APPLICATION_NAME); + setApplicationVersion(MM::VERSION); + setOrganizationName(MM::ORGANIZATION_NAME); + setOrganizationDomain(MM::ORGANIZATION_DOMAIN); } MainApplication::~MainApplication() { + // Deinitialize GStreamer. + gst_deinit(); } bool MainApplication::notify(QObject *receiver, QEvent *event) diff --git a/MainApplication.h b/MainApplication.h index 5464bc8..6ceaa8f 100644 --- a/MainApplication.h +++ b/MainApplication.h @@ -21,8 +21,10 @@ #ifndef MAINAPPLICATION_H_ #define MAINAPPLICATION_H_ +#include #include #include +#include "MM.h" class MainApplication : public QApplication { diff --git a/MainWindow.cpp b/MainWindow.cpp index f61fa99..4dc8532 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -86,7 +86,6 @@ MainWindow::~MainWindow() void MainWindow::handlePaintItemSelectionChanged() { - // Set current paint. QListWidgetItem* item = paintList->currentItem(); currentSelectedItem = item; @@ -97,13 +96,12 @@ void MainWindow::handlePaintItemSelectionChanged() if (paintItemSelected) { // Set current paint. - uid idx = getItemId(*item); - if (currentPaintId != idx) { - setCurrentPaint(idx); - // Unselect current mapping. + uid paintId = getItemId(*item); + // Unselect current mapping. + if (currentPaintId != paintId) removeCurrentMapping(); - mappingList->clearSelection(); - } + // Set current paint. + setCurrentPaint(paintId); } else removeCurrentPaint(); @@ -121,23 +119,19 @@ void MainWindow::handleMappingItemSelectionChanged() { if (mappingList->selectedItems().empty()) { - removeCurrentMapping(); + removeCurrentMapping(); } else { - QListWidgetItem* item = mappingList->currentItem(); + QListWidgetItem* item = mappingList->currentItem(); + currentSelectedItem = item; - currentSelectedItem = item; - // Get current mapping uid. - uid idm = getItemId(*item); - // Set current paint and mappings. - Mapping::ptr mapping = mappingManager->getMappingById(idm); - uid paintId = mapping->getPaint()->getId(); - if (currentPaintId != paintId) { - setCurrentPaint(paintId); - } - - setCurrentMapping(mapping->getId()); + // Set current paint and mappings. + uid mappingId = getItemId(*item); + Mapping::ptr mapping = mappingManager->getMappingById(mappingId); + uid paintId = mapping->getPaint()->getId(); + setCurrentMapping(mappingId); + setCurrentPaint(paintId); } @@ -173,6 +167,7 @@ void MainWindow::handleMappingIndexesMoved() void MainWindow::handleItemSelected(QListWidgetItem* item) { + Q_UNUSED(item); // Change currently selected item. currentSelectedItem = item; } @@ -538,8 +533,7 @@ void MainWindow::about() // Pop-up about dialog. QMessageBox::about(this, tr("About MapMap"), tr("

%1

" - "

Copyright © 2013 Sofian Audry, Alexandre Quessy, " - "Mike Latona and Vasilis Liaskovitis.

" + "

Copyright © 2013 %2.

" "

MapMap is a free software for video mapping.

" "

Projection mapping, also known as video mapping and spatial augmented reality, " "is a projection technology used to turn objects, often irregularly shaped, into " @@ -556,7 +550,7 @@ void MainWindow::about() "La Francophonie.

" "

http://mapmap.info
" "http://www.francophonie.org

" - ).arg(MM::VERSION)); + ).arg(MM::VERSION, MM::COPYRIGHT_OWNERS)); // Restart video playback. XXX Hack videoTimer->start(); @@ -569,24 +563,34 @@ void MainWindow::updateStatusBar() // formulaLabel->setText(spreadsheet->currentFormula()); } +/** + * Called when the user wants to delete an item. + * + * Deletes either a Paint or a Mapping. + */ void MainWindow::deleteItem() { + bool isMappingTabSelected = (mappingSplitter == contentTab->currentWidget()); + bool isPaintTabSelected = (paintSplitter == contentTab->currentWidget()); + if (currentSelectedItem) { - if (currentSelectedItem->listWidget() == mappingList) + if (isMappingTabSelected) //currentSelectedItem->listWidget() == mappingList) { // Delete mapping. deleteMapping( getItemId(*mappingList->currentItem()) ); //currentSelectedItem = NULL; } - else if (currentSelectedItem->listWidget() == paintList) + else if (isPaintTabSelected) //currentSelectedItem->listWidget() == paintList) { // Delete paint. deletePaint( getItemId(*paintList->currentItem()), false ); //currentSelectedItem = NULL; } else + { qCritical() << "Selected item neither a mapping nor a paint." << endl; + } } } @@ -1214,6 +1218,14 @@ void MainWindow::createActions() // Output window should be displayed for full screen option to be available. connect(displayOutputWindow, SIGNAL(toggled(bool)), outputWindowFullScreen, SLOT(setEnabled(bool))); + + // outputWindowHasCursor = new QAction(tr("O&utput window has cursor"), this); + // outputWindowHasCursor->setStatusTip(tr("Show cursor in output window")); + // outputWindowHasCursor->setIconVisibleInMenu(false); + // outputWindowHasCursor->setCheckable(true); + // outputWindowHasCursor->setChecked(true); + // connect(outputWindowHasCursor, SIGNAL(toggled(bool)), outputWindow, SLOT(setFullScreen(bool))); + // Toggle display of canvas controls. displayCanvasControls = new QAction(tr("&Display canvas controls"), this); // displayCanvasControls->setShortcut(tr("Ctrl+E")); @@ -1241,9 +1253,14 @@ void MainWindow::createActions() connect(stickyVertices, SIGNAL(toggled(bool)), outputWindow->getCanvas(), SLOT(enableStickyVertices(bool))); } -void MainWindow::enableFullscreen() +void MainWindow::startFullScreen() { - outputWindowFullScreen->setEnabled(true); + // Remove canvas controls. + displayCanvasControls->setChecked(false); + // Display output window. + displayOutputWindow->setChecked(true); + // Send fullscreen. + outputWindowFullScreen->setChecked(true); } void MainWindow::createMenus() @@ -1298,6 +1315,7 @@ void MainWindow::createMenus() viewMenu->addAction(outputWindowFullScreen); viewMenu->addAction(displayCanvasControls); viewMenu->addAction(stickyVertices); + //viewMenu->addAction(outputWindowHasCursor); // Run. runMenu = menuBar->addMenu(tr("&Run")); @@ -2048,35 +2066,42 @@ QIcon MainWindow::createImageIcon(const QString& filename) { void MainWindow::setCurrentPaint(int uid) { - if (currentPaintId != uid) { - currentPaintId = uid; - paintList->setCurrentRow( getItemRowFromId(*paintList, uid) ); - - if (uid != NULL_UID) + if (uid == NULL_UID) + removeCurrentPaint(); + else { + if (currentPaintId != uid) { + currentPaintId = uid; + paintList->setCurrentRow( getItemRowFromId(*paintList, uid) ); paintPropertyPanel->setCurrentWidget(paintGuis[uid]->getPropertiesEditor()); + } + _hasCurrentPaint = true; } - _hasCurrentPaint = true; } void MainWindow::setCurrentMapping(int uid) { - if (currentMappingId != uid) { - mappingList->setCurrentRow( getItemRowFromId(*mappingList, uid) ); - currentMappingId = uid; - if (uid != NULL_UID) + if (uid == NULL_UID) + removeCurrentMapping(); + else { + if (currentMappingId != uid) { + currentMappingId = uid; + mappingList->setCurrentRow( getItemRowFromId(*mappingList, uid) ); mappingPropertyPanel->setCurrentWidget(mappers[uid]->getPropertiesEditor()); } - _hasCurrentMapping = true; + _hasCurrentMapping = true; + } } void MainWindow::removeCurrentPaint() { _hasCurrentPaint = false; currentPaintId = NULL_UID; + paintList->clearSelection(); } void MainWindow::removeCurrentMapping() { _hasCurrentMapping = false; currentMappingId = NULL_UID; + mappingList->clearSelection(); } void MainWindow::startOscReceiver() diff --git a/MainWindow.h b/MainWindow.h index 3cc7da1..14b8991 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -265,6 +265,7 @@ private: QAction *rewindAction; QAction *displayOutputWindow; + //QAction *outputWindowHasCursor; QAction *outputWindowFullScreen; QAction *displayCanvasControls; QAction *stickyVertices; @@ -347,7 +348,7 @@ public: void removeCurrentPaint(); void removeCurrentMapping(); - void enableFullscreen(); + void startFullScreen(); void setOscPort(QString portNumber); public: // Constants. /////////////////////////////////////////////////////////////////////////////////////// diff --git a/Mapper.cpp b/Mapper.cpp index e5442b0..63cd193 100644 --- a/Mapper.cpp +++ b/Mapper.cpp @@ -324,12 +324,14 @@ void TextureMapper::_preDraw(QPainter* painter) glBindTexture(GL_TEXTURE_2D, texture->getTextureId()); // Copy bits to texture iff necessary. + texture->lockMutex(); if (texture->bitsHaveChanged()) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->getWidth(), texture->getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, texture->getBits()); } + texture->unlockMutex(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); diff --git a/MediaImpl.cpp b/MediaImpl.cpp index 57a3fca..a989ddf 100644 --- a/MediaImpl.cpp +++ b/MediaImpl.cpp @@ -52,9 +52,16 @@ int MediaImpl::getHeight() const return _padHandlerData.height; } -const uchar* MediaImpl::getBits() const +const uchar* MediaImpl::getBits() { - return _data; + // Reset bits changed. + _bitsChanged = false; + + // Return data. + if (_currentFrameSample == NULL) + return NULL; + else + return _data; } QString MediaImpl::getUri() const @@ -83,49 +90,20 @@ void MediaImpl::build() MediaImpl::~MediaImpl() { + // Free all resources. freeResources(); - /* _data points to gstreamer-allocated data, we don't manage it ourselves */ - //if (_data) - // free(_data); -} -bool MediaImpl::_videoPull() -{ - GstSample *sample = _queueInputBuffer.get(); - - if (sample == NULL) - { - // Either means we are not playing or we have reached EOS. - return false; - } - else - { - // Pull current frame buffer. - GstBuffer *buffer = gst_sample_get_buffer(sample); - - GstMapInfo map; - if (gst_buffer_map(buffer, &map, GST_MAP_READ)) - { - // For debugging: - //gst_util_dump_mem(map.data, map.size) - _data = map.data; - gst_buffer_unmap(buffer, &map); - if(this->_frame != NULL) - _queueOutputBuffer.put(this->_frame); - _frame = sample; - } - - return true; - } + // Free mutex locker object. + delete _mutexLocker; } bool MediaImpl::_eos() const { if (_movieReady) { - Q_ASSERT( _videoSink ); + Q_ASSERT( _appsink0 ); gboolean videoEos; - g_object_get (G_OBJECT (_videoSink), "eos", &videoEos, NULL); + g_object_get (G_OBJECT (_appsink0), "eos", &videoEos, NULL); return (bool) (videoEos); } else @@ -134,14 +112,49 @@ bool MediaImpl::_eos() const GstFlowReturn MediaImpl::gstNewSampleCallback(GstElement*, MediaImpl *p) { - GstSample *sample; - sample = gst_app_sink_pull_sample(GST_APP_SINK(p->_videoSink)); - //g_signal_emit_by_name (p->_videoSink, "pull-sample", &sample); - p->getQueueInputBuffer()->put(sample); - if (p->getQueueOutputBuffer()->size() > 1) { - sample = p->getQueueOutputBuffer()->get(); - gst_sample_unref(sample); + // Make it thread-safe. + p->lockMutex(); + + // Get next frame. + GstSample *sample = gst_app_sink_pull_sample(GST_APP_SINK(p->_appsink0)); + + // Unref last frame. + p->_freeCurrentSample(); + + // Set current frame. + p->_currentFrameSample = sample; + + // For live sources, video dimensions have not been set, because + // gstPadAddedCallback is never called. Fix dimensions from first sample / + // caps we receive. + if (p->_isSharedMemorySource && ( p->_padHandlerData.width == -1 || + p->_padHandlerData.height == -1)) { + GstCaps *caps = gst_sample_get_caps(sample); + GstStructure *structure; + structure = gst_caps_get_structure(caps, 0); + gst_structure_get_int(structure, "width", &p->_padHandlerData.width); + gst_structure_get_int(structure, "height", &p->_padHandlerData.height); + // g_print("Size is %u x %u\n", _padHandlerData.width, _padHandlerData.height); } + + // Try to retrieve data bits of frame. + GstMapInfo& map = p->_mapInfo; + GstBuffer *buffer = gst_sample_get_buffer( sample ); + if (gst_buffer_map(buffer, &map, GST_MAP_READ)) + { + p->_currentFrameBuffer = buffer; + // For debugging: + //gst_util_dump_mem(map.data, map.size) + + // Retrieve data from map info. + p->_data = map.data; + + // Bits have changed. + p->_bitsChanged = true; + } + + p->unlockMutex(); + return GST_FLOW_OK; } @@ -149,24 +162,29 @@ MediaImpl::MediaImpl(const QString uri, bool live) : _currentMovie(""), _bus(NULL), _pipeline(NULL), -_source(NULL), -_videoQueue(NULL), -_videoConvert(NULL), -_videoColorSpace(NULL), -_audioSink(NULL), -_videoSink(NULL), -_frame(NULL), -_width(640), -_height(480), +_uridecodebin0(NULL), +_queue0(NULL), +_videoconvert0(NULL), +//_audioSink(NULL), +_appsink0(NULL), +_currentFrameSample(NULL), +_currentFrameBuffer(NULL), +_bitsChanged(false), +_width(640), // unused +_height(480), // unused _data(NULL), _seekEnabled(false), -_live(live), +_isSharedMemorySource(live), _attached(false), _movieReady(false), _uri(uri) { + _pollSource = NULL; if (uri != "") + { loadMovie(uri); + } + _mutexLocker = new QMutexLocker(&_mutex); } void MediaImpl::unloadMovie() @@ -198,26 +216,40 @@ void MediaImpl::freeResources() _pipeline = NULL; } - _source = NULL; - _videoQueue = NULL; - _videoConvert = NULL; - _videoColorSpace = NULL; - _audioSink = NULL; - _videoSink = NULL; - _frame = NULL; + // Reset pipeline elements. + _uridecodebin0 = NULL; + _queue0 = NULL; + _videoconvert0 = NULL; + _appsink0 = NULL; + + // Reset pad handler. _padHandlerData = GstPadHandlerData(); + + // Unref the shmsrc poller. + if (_pollSource) + { + g_source_unref(_pollSource); + _pollSource = NULL; + } + + qDebug() << "Freeing remaining samples/buffers" << endl; + + // Frees current sample and buffer. + _freeCurrentSample(); + + // Resets bits changed. + _bitsChanged = false; } void MediaImpl::resetMovie() { - // TODO: Check if we can still seek when we reach EOS. It seems like it's then impossible and we - // have to reload but it seems weird so we should check. - if (!_eos() && _seekEnabled) + // XXX: There used to be an issue that when we reached EOS (_eos() == true) we could not seek anymore. + if (_seekEnabled) { qDebug() << "Seeking at position 0."; gst_element_seek_simple (_pipeline, GST_FORMAT_TIME, (GstSeekFlags) (GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), 0); - this->_frame = NULL; + this->_currentFrameSample = NULL; _setReady(true); } else @@ -233,8 +265,11 @@ gboolean gstPollShmsrc (void *user_data) { MediaImpl *p = (MediaImpl*) user_data; - if (g_file_test(p->getUri().toUtf8().constData() , G_FILE_TEST_EXISTS) && !p->getAttached()) { - if (!p->setPlayState(true)) { + if (g_file_test(p->getUri().toUtf8().constData(), G_FILE_TEST_EXISTS) && + ! p->getAttached()) + { + if (! p->setPlayState(true)) + { qDebug() << "tried to attach, but starting pipeline failed!" << endl; return false; } @@ -255,56 +290,108 @@ bool MediaImpl::loadMovie(QString filename) _uri = filename; qDebug() << "Opening movie: " << filename << "."; - this->_frame = NULL; // Free previously allocated structures unloadMovie(); // Initialize GStreamer. - gst_init (NULL, NULL); - GstElement *capsFilter = NULL; - GstElement *videoScale = NULL; + GstElement *capsfilter0 = NULL; + GstElement *videoscale0 = NULL; // Create the elements. - _source = gst_element_factory_make ("uridecodebin", "source"); - _videoQueue = gst_element_factory_make ("queue", "vqueue"); - _videoColorSpace = gst_element_factory_make ("videoconvert", "vcolorspace"); - videoScale = gst_element_factory_make ("videoscale", "videoscale0"); - capsFilter = gst_element_factory_make ("capsfilter", "capsfilter0"); - _videoSink = gst_element_factory_make ("appsink", "vsink"); + if (_isSharedMemorySource) + { + _shmsrc0 = gst_element_factory_make ("shmsrc", "shmsrc0"); + _gdpdepay0 = gst_element_factory_make ("gdpdepay", "gdpdepay0"); + _pollSource = g_timeout_source_new (500); + g_source_set_callback (_pollSource, + gstPollShmsrc, + this, + NULL); + g_source_attach (_pollSource, g_main_context_default()); + g_source_unref (_pollSource); + } + else { + _uridecodebin0 = gst_element_factory_make ("uridecodebin", "uridecodebin0"); + } + _queue0 = gst_element_factory_make ("queue", "queue0"); + _videoconvert0 = gst_element_factory_make ("videoconvert", "videoconvert0"); + videoscale0 = gst_element_factory_make ("videoscale", "videoscale0"); + capsfilter0 = gst_element_factory_make ("capsfilter", "capsfilter0"); + _appsink0 = gst_element_factory_make ("appsink", "appsink0"); // Prepare handler data. - _padHandlerData.videoToConnect = _videoQueue; - _padHandlerData.videoSink = _videoSink; + _padHandlerData.videoToConnect = _queue0; + _padHandlerData.videoSink = _appsink0; _padHandlerData.videoIsConnected = false; // Create the empty pipeline. _pipeline = gst_pipeline_new ( "video-source-pipeline" ); - if (!_pipeline || !_source || - !_videoQueue || !_videoColorSpace || ! videoScale || ! capsFilter || ! _videoSink) + if (!_pipeline || + !_queue0 || !_videoconvert0 || ! videoscale0 || ! capsfilter0 || ! _appsink0) { g_printerr ("Not all elements could be created.\n"); + + if (! _pipeline) g_printerr("_pipeline"); + if (! _queue0) g_printerr("_queue0"); + if (! _videoconvert0) g_printerr("_videoconvert0"); + if (! videoscale0) g_printerr("videoscale0"); + if (! capsfilter0) g_printerr("capsfilter0"); + if (! _appsink0) g_printerr("_appsink0"); + unloadMovie(); return -1; } + + if (_isSharedMemorySource) + { + if (! _shmsrc0 || ! _gdpdepay0) + { + g_printerr ("Not all elements could be created.\n"); + if (! _shmsrc0) g_printerr("_shmsrc0"); + if (! _gdpdepay0) g_printerr("_gdpdepay0"); + unloadMovie(); + return -1; + } + } + else + { + if (! _uridecodebin0) + { + g_printerr ("Not all elements could be created.\n"); + if (! _uridecodebin0) g_printerr("_uridecodebin0"); + unloadMovie(); + return -1; + } + } // Build the pipeline. Note that we are NOT linking the source at this // point. We will do it later. - gst_bin_add_many (GST_BIN (_pipeline), _source, - _videoQueue, _videoColorSpace, videoScale, capsFilter, _videoSink, NULL); + gst_bin_add_many (GST_BIN (_pipeline), + _isSharedMemorySource ? _shmsrc0 : _uridecodebin0, _queue0, + _videoconvert0, videoscale0, capsfilter0, _appsink0, NULL); - if (_live) { - gst_bin_add (GST_BIN(_pipeline), _deserializer); - if (!gst_element_link_many (_source, _deserializer, _videoQueue, NULL)) { - g_printerr ("Video elements could not be linked.\n"); + // special case for shmsrc + if (_isSharedMemorySource) + { + gst_bin_add (GST_BIN(_pipeline), _gdpdepay0); + if (! gst_element_link_many (_shmsrc0, _gdpdepay0, _queue0, NULL)) + { + g_printerr ("Could not link shmsrc, deserializer and video queue.\n"); + } + } + else + { + if (! gst_element_link (_uridecodebin0, _queue0)) + { + g_printerr ("Could not link uridecodebin to video queue.\n"); } } - else if (!gst_element_link (_source, _videoQueue)) - g_printerr ("Video elements could not be linked.\n"); - if (!gst_element_link_many (_videoQueue, _videoColorSpace, capsFilter, videoScale, _videoSink, NULL)) { - g_printerr ("Video elements could not be linked.\n"); + if (! gst_element_link_many (_queue0, _videoconvert0, capsfilter0, videoscale0, _appsink0, NULL)) + { + g_printerr ("Could not link video queue, colorspace converter, caps filter, scaler and app sink.\n"); unloadMovie(); return false; } @@ -312,12 +399,13 @@ bool MediaImpl::loadMovie(QString filename) // Process URI. QByteArray ba = filename.toLocal8Bit(); gchar* uri = (gchar*) filename.toUtf8().constData(); - if (!_live && !gst_uri_is_valid(uri)) + if (! _isSharedMemorySource && ! gst_uri_is_valid(uri)) { // Try to convert filename to URI. GError* error = NULL; uri = gst_filename_to_uri(uri, &error); - if (error) { + if (error) + { qDebug() << "Filename to URI error: " << error->message; g_error_free(error); gst_object_unref (uri); @@ -326,23 +414,26 @@ bool MediaImpl::loadMovie(QString filename) } } - if (_live) + if (_isSharedMemorySource) + { uri = (gchar*) ba.data(); + } // Set URI to be played. qDebug() << "URI for uridecodebin: " << uri; // FIXME: sometimes it's just the path to the directory that is given, not the file itself. - // Connect to the pad-added signal - if (!_live) { - g_object_set (_source, "uri", uri, NULL); - g_signal_connect (_source, "pad-added", G_CALLBACK (MediaImpl::gstPadAddedCallback), &_padHandlerData); + if (! _isSharedMemorySource) + { + g_object_set (_uridecodebin0, "uri", uri, NULL); + g_signal_connect (_uridecodebin0, "pad-added", G_CALLBACK (MediaImpl::gstPadAddedCallback), &_padHandlerData); } - else { + else + { //qDebug() << "LIVE mode" << uri; - g_object_set (_source, "socket-path", uri, NULL); - g_object_set (_source, "is-live", TRUE, NULL); + g_object_set (_shmsrc0, "socket-path", uri, NULL); + g_object_set (_shmsrc0, "is-live", TRUE, NULL); _padHandlerData.videoIsConnected = true; } @@ -362,64 +453,58 @@ bool MediaImpl::loadMovie(QString filename) // Configure video appsink. GstCaps *videoCaps = gst_caps_from_string ("video/x-raw,format=RGBA"); - g_object_set (capsFilter, "caps", videoCaps, NULL); - g_object_set (_videoSink, "emit-signals", TRUE, + g_object_set (capsfilter0, "caps", videoCaps, NULL); + g_object_set (_appsink0, "emit-signals", TRUE, "max-buffers", 1, // only one buffer (the last) is maintained in the queue "drop", TRUE, // ... other buffers are dropped "sync", TRUE, NULL); - g_signal_connect (_videoSink, "new-sample", G_CALLBACK (MediaImpl::gstNewSampleCallback), this); + g_signal_connect (_appsink0, "new-sample", G_CALLBACK (MediaImpl::gstNewSampleCallback), this); gst_caps_unref (videoCaps); // Listen to the bus. _bus = gst_element_get_bus (_pipeline); // Start playing. - if (!_live && !setPlayState(true)) + if (! _isSharedMemorySource && ! setPlayState(true)) + { return false; - + } qDebug() << "Pipeline started."; //_movieReady = true; return true; } -bool MediaImpl::runVideo() { - - if (!_preRun()) - return false; - - bool bitsChanged = false; - - // Check if we have some frames in the input buffer. - if (_queueInputBuffer.size() > 0) { - - // Pull video. - if (_videoPull()) - bitsChanged = true; - - //std::cout << "VideoImpl::runVideo: read frame #" << _videoNewBufferCounter << std::endl; +void MediaImpl::update() +{ + // Check for end-of-stream or terminate. + if (_eos() || _terminate) + { + _setFinished(true); + resetMovie(); } - /* TODO: This causes the texture to be loaded always in Mapper.cpp . The - * problem if this is not set is: When we have more than one shape, a - * shape that has a new buffer coming in will overdraw the old buffer of the - * shape on top. This implementation seems to be fast enough that - * _videoNewBufferCounter is often 1 or 0. If bitsChanged is often switching - * between true and false (as in the case described above), than the shape - * textures will appear to be flickering/alternating. Maybe a better solution is - * needed (in the GL layer or here?)*/ else - bitsChanged = true; + { + _setFinished(false); + } - _postRun(); - - return bitsChanged; +// // Check if movie is ready and connected. +// if (!isReady()) +// { +// _bitsChanged = false; +// } +// + // Check gstreamer messages on bus. + _checkMessages(); } bool MediaImpl::setPlayState(bool play) { if (_pipeline == NULL) + { return false; + } GstStateChangeReturn ret = gst_element_set_state (_pipeline, (play ? GST_STATE_PLAYING : GST_STATE_PAUSED)); if (ret == GST_STATE_CHANGE_FAILURE) @@ -445,15 +530,18 @@ bool MediaImpl::_preRun() resetMovie(); } else + { _setFinished(false); + } if (!_movieReady || !_padHandlerData.videoIsConnected) + { return false; - + } return true; } -void MediaImpl::_postRun() +void MediaImpl::_checkMessages() { // Parse message. if (_bus != NULL) @@ -462,12 +550,13 @@ void MediaImpl::_postRun() _bus, 0, (GstMessageType) (GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS)); - if (msg != NULL) { + if (msg != NULL) + { GError *err; gchar *debug_info; - switch (GST_MESSAGE_TYPE (msg)) { - + switch (GST_MESSAGE_TYPE (msg)) + { case GST_MESSAGE_ERROR: gst_message_parse_error(msg, &err, &debug_info); g_printerr("Error received from element %s: %s\n", @@ -477,9 +566,12 @@ void MediaImpl::_postRun() g_clear_error(&err); g_free(debug_info); - if (!_live) + if (!_isSharedMemorySource) + { _terminate = true; - else { + } + else + { _attached = false; gst_element_set_state (_pipeline, GST_STATE_PAUSED); gst_element_set_state (_pipeline, GST_STATE_NULL); @@ -489,7 +581,9 @@ void MediaImpl::_postRun() break; case GST_MESSAGE_EOS: + // Automatically loop back. g_print("End-Of-Stream reached.\n"); + resetMovie(); // _terminate = true; // _finish(); break; @@ -552,12 +646,33 @@ void MediaImpl::_setReady(bool ready) _movieReady = ready; } -void MediaImpl::_setFinished(bool finished) { +void MediaImpl::_setFinished(bool finished) +{ Q_UNUSED(finished); // qDebug() << "Clip " << (finished ? "finished" : "not finished"); } -void MediaImpl::gstPadAddedCallback(GstElement *src, GstPad *newPad, MediaImpl::GstPadHandlerData* data) { +void MediaImpl::_freeCurrentSample() { + if (_currentFrameBuffer != NULL) + { + gst_buffer_unmap(_currentFrameBuffer, &_mapInfo); + } + + if (_currentFrameSample != NULL) + { + gst_sample_unref(_currentFrameSample); + } + + _currentFrameSample = NULL; + _currentFrameBuffer = NULL; + _data = NULL; +} + +/** + * FIXME: remove GOTO + */ +void MediaImpl::gstPadAddedCallback(GstElement *src, GstPad *newPad, MediaImpl::GstPadHandlerData* data) +{ g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (newPad), GST_ELEMENT_NAME (src)); GstPad *sinkPad = NULL; @@ -565,7 +680,9 @@ void MediaImpl::gstPadAddedCallback(GstElement *src, GstPad *newPad, MediaImpl:: GstCaps *newPadCaps = gst_pad_query_caps (newPad, NULL); GstStructure *newPadStruct = gst_caps_get_structure (newPadCaps, 0); const gchar *newPadType = gst_structure_get_name (newPadStruct); - g_print("Structure is %s\n", gst_structure_to_string(newPadStruct)); + gchar *newPadStructStr = gst_structure_to_string(newPadStruct); + g_print("Structure is %s\n", newPadStructStr); + g_free(newPadStructStr); if (g_str_has_prefix (newPadType, "video/x-raw")) { sinkPad = gst_element_get_static_pad (data->videoToConnect, "sink"); @@ -598,7 +715,8 @@ void MediaImpl::gstPadAddedCallback(GstElement *src, GstPad *newPad, MediaImpl:: } // Attempt the link - if (GST_PAD_LINK_FAILED (gst_pad_link (newPad, sinkPad))) { + if (GST_PAD_LINK_FAILED (gst_pad_link (newPad, sinkPad))) + { g_print (" Type is '%s' but link failed.\n", newPadType); goto exit; } else { @@ -609,9 +727,24 @@ void MediaImpl::gstPadAddedCallback(GstElement *src, GstPad *newPad, MediaImpl:: exit: // Unreference the new pad's caps, if we got them. if (newPadCaps != NULL) + { gst_caps_unref (newPadCaps); + } // Unreference the sink pad. if (sinkPad != NULL) + { gst_object_unref (sinkPad); + } } + +void MediaImpl::lockMutex() +{ + _mutexLocker->relock(); +} + +void MediaImpl::unlockMutex() +{ + _mutexLocker->unlock(); +} + diff --git a/MediaImpl.h b/MediaImpl.h index b48a94b..4f6f098 100644 --- a/MediaImpl.h +++ b/MediaImpl.h @@ -30,13 +30,14 @@ #include #include #include +#include +#include #if __APPLE__ #include #else #include #endif #include -#include "AsyncQueue.h" /** * Private declaration of the video player. @@ -46,25 +47,82 @@ class MediaImpl { public: + /** + * Constructor. + * This media player works for both video files and shared memory sockets. + * If live is true, it's a shared memory socket. + */ MediaImpl(const QString uri, bool live); ~MediaImpl(); // void setUri(const QString uri); + /** + * Returns whether or not GStreamer video support is ok. + */ static bool hasVideoSupport(); + + /** + * Sets up the player. + * Basically calls loadMovie(). + */ void build(); + + /** + * Returns the width of the video image. + */ int getWidth() const; + + /** + * Returns the height of the video image. + */ int getHeight() const; + + /** + * Returns the path to the media file being played. + */ QString getUri() const; + + /** + * When using the shared memory source, returns whether or not we + * are attached to a shared memory socket. + */ bool getAttached(); - const uchar* getBits() const; - bool isReady() const { return _padHandlerData.videoIsConnected; } + /** + * Returns the raw image of the last video frame. + * It is currently unused! + */ + const uchar* getBits(); - /// Returns true if the image has changed. - bool runVideo(); -// void runAudio(); + /// Returns true iff bits have changed since last call to getBits(). + bool bitsHaveChanged() const { return _bitsChanged; } + + /** + * Checks if the pipeline is ready. + * + * Returns whether or not the elements in the pipeline are connected, + * and if we are using shmsrc, if the shared memory socket is being read. + */ + bool isReady() const { return _movieReady && _padHandlerData.videoIsConnected; } + + /** + * Performs regular updates (checks if movie is ready and checks messages). + */ + void update(); + + // void runAudio(); + + /** + * Loads a new movie file. + * + * Creates a new GStreamer pipeline, opens a movie or a shmsrc socket. + */ bool loadMovie(QString filename); bool setPlayState(bool play); + /** + * Tells the MediaImpl that we are actually reading from a shmsrc. + * Called from the GStreamer callback of the shmsrc. + */ void setAttached(bool attach); void resetMovie(); @@ -74,16 +132,23 @@ protected: void freeResources(); private: - bool _videoPull(); + /** + * Checks if we reached the end of the video file. + * + * Returns false if the pipeline is not ready yet. + */ bool _eos() const; -// void _finish(); -// void _init(); + + // void _finish(); + // void _init(); bool _preRun(); - void _postRun(); + void _checkMessages(); void _setReady(bool ready); void _setFinished(bool finished); + void _freeCurrentSample(); + public: // GStreamer callbacks. @@ -108,54 +173,84 @@ public: // GStreamer callback that plugs the audio/video pads into the proper elements when they // are made available by the source. static void gstPadAddedCallback(GstElement *src, GstPad *newPad, MediaImpl::GstPadHandlerData* data); - AsyncQueue *getQueueInputBuffer() { - return &this->_queueInputBuffer; - } - AsyncQueue *getQueueOutputBuffer() { - return &this->_queueOutputBuffer; - } + /// Locks mutex (default = no effect). + void lockMutex(); + + /// Unlocks mutex (default = no effect). + void unlockMutex(); private: //locals + /** + * Path of the movie being played. + */ QString _currentMovie; - // gstreamer + // gstreamer elements GstBus *_bus; GstElement *_pipeline; - GstElement *_source; - GstElement *_deserializer; - GstElement *_audioQueue; - GstElement *_audioConvert; - GstElement *_audioResample; - GstElement *_videoQueue; - GstElement *_videoConvert; - GstElement *_videoColorSpace; - GstElement *_audioSink; - GstElement *_videoSink; - GstSample *_frame; + GstElement *_uridecodebin0; + GstElement *_shmsrc0; + GstElement *_gdpdepay0; + //GstElement *_audioQueue; + //GstElement *_audioConvert; + //GstElement *_audioResample; + GstElement *_queue0; + GstElement *_videoconvert0; + //GstElement *_audioSink; + GstElement *_appsink0; + + /** + * Temporary contains the image data of the last frame. + */ + GstSample *_currentFrameSample; + GstBuffer *_currentFrameBuffer; + GstMapInfo _mapInfo; + bool _bitsChanged; + + /** + * shmsrc socket poller. + */ GSource *_pollSource; GstPadHandlerData _padHandlerData; + // unused int _width; + // unused int _height; + + /** + * Raw image data of the last video frame. + */ uchar *_data; bool _seekEnabled; - bool _live; + /** + * Whether or not we are reading video from a shmsrc. + */ + bool _isSharedMemorySource; + /** + * Whether or not we are attached to a shmsrc, if using a shmsrc. + */ bool _attached; int _audioNewBufferCounter; bool _terminate; bool _movieReady; - AsyncQueue _queueInputBuffer; - AsyncQueue _queueOutputBuffer; + + QMutex _mutex; + QMutexLocker* _mutexLocker; private: + /** + * Path of the movie file being played. + */ QString _uri; + static const int MAX_SAMPLES_IN_BUFFER_QUEUES = 30; }; #endif /* ifndef */ diff --git a/OutputGLWindow.cpp b/OutputGLWindow.cpp index e9ce620..4a58770 100644 --- a/OutputGLWindow.cpp +++ b/OutputGLWindow.cpp @@ -36,6 +36,24 @@ OutputGLWindow::OutputGLWindow(MainWindow* mainWindow, QWidget* parent, const QG // Save window geometry. _geometry = saveGeometry(); + + _pointerIsVisible = true; +} + +void OutputGLWindow::setCursorVisible(bool visible) +{ + _pointerIsVisible = visible; + + if (_pointerIsVisible) + { + this->setCursor(Qt::ArrowCursor); + _pointerIsVisible = true; + } + else + { + this->setCursor(Qt::BlankCursor); + _pointerIsVisible = false; + } } void OutputGLWindow::closeEvent(QCloseEvent *event) @@ -47,14 +65,16 @@ void OutputGLWindow::closeEvent(QCloseEvent *event) void OutputGLWindow::keyPressEvent(QKeyEvent *event) { // Escape from full screen mode. - if (isFullScreen() && event->key() == Qt::Key_Escape) + if (event->key() == Qt::Key_Escape) { - setFullScreen(false); - emit fullScreenToggled(false); - } - else if (event->key() == Qt::Key_Escape) - { - // pass + if (isFullScreen()) + { + setFullScreen(false); + emit fullScreenToggled(false); + } else { + setFullScreen(true); + emit fullScreenToggled(true); + } } else { diff --git a/OutputGLWindow.h b/OutputGLWindow.h index 8c85572..768df62 100644 --- a/OutputGLWindow.h +++ b/OutputGLWindow.h @@ -2,6 +2,7 @@ * OutputGLWindow.h * * (c) 2014 Sofian Audry -- info(@)sofianaudry(.)com + * (c) 2014 Alexandre Quessy -- alexandre(@)quessy(.)net * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +23,7 @@ #include #include +#include #include "DestinationGLCanvas.h" // TODO: add SLOT for mySetVisible @@ -47,10 +49,14 @@ signals: public: DestinationGLCanvas* getCanvas() const { return canvas; } + void setPointerHasMoved(); + void setCursorVisible(bool visible); private: DestinationGLCanvas* canvas; QByteArray _geometry; + + bool _pointerIsVisible; }; #endif /* OutputGLWINDOW_H_ */ diff --git a/Paint.cpp b/Paint.cpp index 6a221b2..74b6af2 100644 --- a/Paint.cpp +++ b/Paint.cpp @@ -86,8 +86,7 @@ int Media::getHeight() const } void Media::update() { - if (impl_->runVideo()) - bitsChanged = true; + impl_->update(); } void Media::play() @@ -105,11 +104,25 @@ void Media::rewind() impl_->resetMovie(); } -const uchar* Media::_getBits() const +void Media::lockMutex() { + impl_->lockMutex(); +} + +void Media::unlockMutex() { + impl_->unlockMutex(); +} + +const uchar* Media::getBits() { return this->impl_->getBits(); } +bool Media::bitsHaveChanged() const +{ + return this->impl_->bitsHaveChanged(); +} + + bool Media::hasVideoSupport() { return MediaImpl::hasVideoSupport(); diff --git a/Paint.h b/Paint.h index d9aa419..3fb7ef3 100644 --- a/Paint.h +++ b/Paint.h @@ -25,8 +25,8 @@ #include #include #include - #include +#include #if __APPLE__ #include @@ -76,6 +76,12 @@ public: /// Rewinds. virtual void rewind() {} + /// Locks mutex (default = no effect). + virtual void lockMutex() {} + + /// Unlocks mutex (default = no effect). + virtual void unlockMutex() {} + void setName(const QString& name) { _name = name; } QString getName() const { return _name; } uid getId() const { return _id; } @@ -118,8 +124,7 @@ protected: Paint(id), textureId(0), x(0), - y(0), - bitsChanged(true) + y(0) { glGenTextures(1, &textureId); } @@ -134,13 +139,12 @@ public: GLuint getTextureId() const { return textureId; } virtual int getWidth() const = 0; virtual int getHeight() const = 0; - virtual const uchar* getBits() const { - bitsChanged = false; - return _getBits(); - } + + /// Returns image bits data. Next call to bitsHaveChanged() will be false. + virtual const uchar* getBits() = 0; /// Returns true iff bits have changed since last call to getBits(). - bool bitsHaveChanged() const { return bitsChanged; } + virtual bool bitsHaveChanged() const = 0; virtual void setPosition(GLfloat xPos, GLfloat yPos) { x = xPos; @@ -148,9 +152,6 @@ public: } virtual GLfloat getX() const { return x; } virtual GLfloat getY() const { return y; } - -protected: - virtual const uchar* _getBits() const = 0; }; /** @@ -184,8 +185,12 @@ public: virtual int getWidth() const { return image.width(); } virtual int getHeight() const { return image.height(); } -protected: - virtual const uchar* _getBits() const { return image.bits(); } + virtual const uchar* getBits() { + bitsChanged = false; + return image.bits(); + } + + virtual bool bitsHaveChanged() const { return bitsChanged; } }; class MediaImpl; // forward declaration @@ -215,6 +220,12 @@ public: /// Rewinds. virtual void rewind(); + /// Locks mutex (default = no effect). + virtual void lockMutex(); + + /// Unlocks mutex (default = no effect). + virtual void unlockMutex(); + virtual QString getType() const { return "media"; @@ -222,14 +233,15 @@ public: virtual int getWidth() const; virtual int getHeight() const; + virtual const uchar* getBits(); + + virtual bool bitsHaveChanged() const; + /** * Checks whether or not video is supported on this platform. */ static bool hasVideoSupport(); -protected: - virtual const uchar* _getBits() const; - private: /** * Private implementation, so that GStreamer headers don't need diff --git a/README b/README index 3fc5bd7..87fa1b4 100644 --- a/README +++ b/README @@ -35,6 +35,7 @@ Authors * Sofian Audry: lead developer, user interface designer, project manager. * Vasilis Liaskovitis: developer. * Mike Latona: user interface designer. +* Dame Diongue: developer. Contributors ------------ diff --git a/main.cpp b/main.cpp index 0a15158..1790185 100644 --- a/main.cpp +++ b/main.cpp @@ -153,7 +153,7 @@ int main(int argc, char *argv[]) if (parser.isSet(fullscreenOption)) { qDebug() << "TODO: Running in fullscreen mode"; - win.enableFullscreen(); + win.startFullScreen(); } #endif