diff --git a/MainWindow.cpp b/MainWindow.cpp index 0d9ff9c..83d1214 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -27,6 +27,11 @@ MainWindow::MainWindow() { // Create model. + if (Video::hasVideoSupport()) + std::cout << "Video support: yes" << std::endl; + else + std::cout << "Video support: no" << std::endl; + mappingManager = new MappingManager; // Initialize internal variables. @@ -38,6 +43,11 @@ MainWindow::MainWindow() currentSelectedItem = NULL; // Create everything. + videoTimer = new QTimer(this); + videoTimer->setInterval(1000/30); + connect(videoTimer, SIGNAL(timeout()), this, SLOT(updateAll())); + videoTimer->start(); + createLayout(); createActions(); createMenus(); @@ -428,7 +438,8 @@ uid MainWindow::createImagePaint(uid paintId, QString uri, float x, float y) else { - Image* img = new Image(uri, paintId); + Video* img = new Video(uri, paintId); +// Image* img = new Image(uri, paintId); // Create new image with corresponding ID. img->setPosition(x, y); @@ -1206,7 +1217,7 @@ void MainWindow::addMappingItem(uid mappingId) // Add mapper. // XXX hardcoded for textures std::tr1::shared_ptr textureMapping; - if (paintType == "image") + if (paintType == "image" || paintType == "video") { textureMapping = std::tr1::static_pointer_cast(mapping); Q_CHECK_PTR(textureMapping); @@ -1347,6 +1358,8 @@ void MainWindow::removePaintItem(uid paintId) // Window was modified. windowModified(); + // Build mapping! + // FIXME: mapping->build(); // I removed this 2014-04-25 } void MainWindow::clearWindow() @@ -1523,3 +1536,10 @@ void MainWindow::applyOscCommand(const QVariantList& command) open(); } +void MainWindow::updateAll() +{ + sourceCanvas->update(); + destinationCanvas->update(); +} + + diff --git a/MainWindow.h b/MainWindow.h index 93bbf10..b83ab41 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -268,6 +268,9 @@ private: // Keeps track of the current selected item, wether it's a paint or mapping. QListWidgetItem* currentSelectedItem; + QTimer *videoTimer; + + static MainWindow* instance; public: // Accessor/mutators for the view. /////////////////////////////////////////////////////////////////// @@ -298,6 +301,9 @@ public: currentMappingId = NULL_UID; } +public slots: + void updateAll(); + public: // Constants. /////////////////////////////////////////////////////////////////////////////////////// static const int DEFAULT_WIDTH = 1600; diff --git a/Mapper.cpp b/Mapper.cpp index 786c43d..dd0395e 100644 --- a/Mapper.cpp +++ b/Mapper.cpp @@ -20,6 +20,7 @@ #include "Mapper.h" #include "MainWindow.h" +#include "unused.h" Mapper::Mapper(Mapping::ptr mapping) : _mapping(mapping) @@ -171,6 +172,7 @@ void PolygonColorMapper::draw(QPainter* painter) void PolygonColorMapper::drawControls(QPainter* painter) { Q_UNUSED(painter); + UNUSED(painter); } MeshColorMapper::MeshColorMapper(Mapping::ptr mapping) @@ -302,6 +304,7 @@ void TextureMapper::draw(QPainter* painter) Q_ASSERT( outputShape->nVertices() == outputShape->nVertices()); // Project source texture and sent it to destination. + texture->update(); glEnable (GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture->getTextureId()); diff --git a/Mapping.h b/Mapping.h index a84a9d1..68db3e7 100644 --- a/Mapping.h +++ b/Mapping.h @@ -71,6 +71,10 @@ public: static const UidAllocator& getUidAllocator() { return allocator; } + /** + * Sets up this Mapping: its Paint and its Shape. + * Calls the build() method of its Paint and Shape. + */ virtual void build() { _paint->build(); _shape->build(); diff --git a/OscInterface.cpp b/OscInterface.cpp index 54ad6e1..95e46f4 100644 --- a/OscInterface.cpp +++ b/OscInterface.cpp @@ -23,6 +23,7 @@ #ifdef HAVE_OSC #include "OscInterface.h" #include "MainWindow.h" +#include "unused.h" #include OscInterface::OscInterface( diff --git a/OscReceiver.cpp b/OscReceiver.cpp index c9b1f3d..93910af 100644 --- a/OscReceiver.cpp +++ b/OscReceiver.cpp @@ -3,6 +3,8 @@ #include #include +bool OscReceiver::server_is_ok_ = true; + OscReceiver::OscReceiver(const std::string &port) : port_(port), server_(lo_server_thread_new(port_.c_str(), error)) @@ -57,6 +59,7 @@ void OscReceiver::error(int num, const char *msg, const char *path) { std::cerr << "liblo server error " << num << " in path " << path << ": " << msg << std::endl; + OscReceiver::server_is_ok_ = false; } // #ifdef CONFIG_DEBUG diff --git a/OscReceiver.h b/OscReceiver.h index d615940..44d1096 100644 --- a/OscReceiver.h +++ b/OscReceiver.h @@ -17,9 +17,10 @@ class OscReceiver { void addHandler(const char *path, const char *types, lo_method_handler handler, void *user_data); + public: + static bool server_is_ok_; private: std::string toString() const; - bool server_is_ok_; std::string port_; lo_server_thread server_; #ifdef CONFIG_DEBUG diff --git a/Paint.cpp b/Paint.cpp index 151b417..1f4d4b8 100644 --- a/Paint.cpp +++ b/Paint.cpp @@ -19,6 +19,8 @@ */ #include "Paint.h" +#include "VideoImpl.h" +#include UidAllocator Paint::allocator; @@ -40,3 +42,50 @@ Paint::~Paint() allocator.free(_id); } + +/* Implementation of the Video class */ + +Video::Video(const QString uri_, uid id): + Texture(id), + uri(uri_), + impl_(NULL) +{ + impl_ = new VideoImpl(uri_); +} + +// vertigo + +Video::~Video() +{ + delete impl_; +} + +void Video::build() +{ + this->impl_->build(); +} + +int Video::getWidth() const +{ + return this->impl_->getWidth(); +} + +int Video::getHeight() const +{ + return this->impl_->getHeight(); +} + +void Video::update() { + impl_->runVideo(); +} + +const uchar* Video::getBits() const +{ + return this->impl_->getBits(); +} + +bool Video::hasVideoSupport() +{ + return VideoImpl::hasVideoSupport(); +} + diff --git a/Paint.h b/Paint.h index 174c80e..b2f3e6c 100644 --- a/Paint.h +++ b/Paint.h @@ -64,6 +64,9 @@ public: virtual void build() {} + /// This method should be called at each call of draw(). + virtual void update() {} + void setName(const QString& name) { _name = name; } QString getName() const { return _name; } uid getId() const { return _id; } @@ -161,4 +164,41 @@ public: virtual const uchar* getBits() const { return image.bits(); } }; +class VideoImpl; // forware declaration + +/** + * Paint that is a Texture retrieved via a video file. + */ +class Video : public Texture +{ +protected: + QString uri; +public: + Video(const QString uri_, uid id=NULL_UID); + virtual ~Video(); + const QString getUri() const + { + return uri; + } + virtual void build(); + virtual void update(); + virtual QString getType() const + { + return "video"; + } + virtual int getWidth() const; + virtual int getHeight() const; + virtual const uchar* getBits() const; + /** + * Checks whether or not video is supported on this platform. + */ + static bool hasVideoSupport(); +private: + /** + * Private implementation, so that GStreamer headers don't need + * to be included from every file in the project. + */ + VideoImpl * impl_; // PIMPL opaque pointer +}; + #endif /* PAINT_H_ */ diff --git a/Util.cpp b/Util.cpp index 82cd979..66d8bf4 100644 --- a/Util.cpp +++ b/Util.cpp @@ -19,6 +19,7 @@ */ #include "Util.h" +#include "unused.h" #include namespace Util { diff --git a/VideoImpl.cpp b/VideoImpl.cpp new file mode 100644 index 0000000..5d6978d --- /dev/null +++ b/VideoImpl.cpp @@ -0,0 +1,726 @@ +/* + * VideoImpl.cpp + * + * (c) 2013 Sofian Audry -- info(@)sofianaudry(.)com + * (c) 2013 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "VideoImpl.h" +#include + +// -------- private implementation of VideoImpl ------- + +bool VideoImpl::hasVideoSupport() +{ + static bool did_print_gst_version = false; + if (! did_print_gst_version) + { + qDebug() << "Using GStreamer version " << + GST_VERSION_MAJOR << "." << + GST_VERSION_MINOR << "." << + GST_VERSION_MICRO << endl; + did_print_gst_version = true; + } + // TODO: actually check if we have it + return true; +} + +int VideoImpl::getWidth() const +{ + return _width; +} + +int VideoImpl::getHeight() const +{ + return _height; +} + +const uchar* VideoImpl::getBits() const +{ + return _data; +} + +void VideoImpl::build() +{ + qDebug() << "Building video impl" << endl; + if (!loadMovie(_uri)) + { + qDebug() << "Cannot load movie " << _currentMovie << "." << endl; + } +} + +VideoImpl::~VideoImpl() +{ + freeResources(); + if (_data) + free(_data); +} + +bool VideoImpl::_videoPull() +{ + qDebug() << "video pull" << endl; + + GstBuffer *buffer; + + // Retrieve the buffer. + g_signal_emit_by_name (_videoSink, "pull-buffer", &buffer); + + if (!buffer) + { + // Either means we are not playing or we have reached EOS. + return false; + } + + else + { + GstCaps* caps = GST_BUFFER_CAPS(buffer); + GstStructure *capsStruct = gst_caps_get_structure (caps, 0); + + int width = 640; + int height = 480; + int bpp = 24; + int depth = 24; + + gst_structure_get_int(capsStruct, "width", &width); + gst_structure_get_int(capsStruct, "height", &height); + gst_structure_get_int(capsStruct, "bpp", &bpp); + gst_structure_get_int(capsStruct, "depth", &depth); + + _width = width; + _height = height; + + if (!_data) + _data = (uchar*)calloc(_width * _height, sizeof(uchar*)); + +// video->resize(width, height); + +// qDebug() << gst_structure_to_string(capsStruct) << endl; +// qDebug() << width << "x" << height << "=" << width*height << "(" << width*height*4 << "," << width*height*3 << ")" << endl; +// qDebug() << "bpp: " << bpp << " depth: " << depth << endl; +// qDebug() << "Buffer size: " << GST_BUFFER_SIZE(buffer) << endl; + + if (bpp == 32) + memcpy(_data, GST_BUFFER_DATA(buffer), _width * _height * 4); + else + convert24to32(_data, GST_BUFFER_DATA(buffer), _width * _height); + + gst_buffer_unref (buffer); + + return true; + } + +} + +bool VideoImpl::_eos() const +{ + if (_movieReady) + { + Q_ASSERT( _videoSink ); +// Q_ASSERT( _audioSink ); + gboolean videoEos; +// gboolean audioEos; + g_object_get (G_OBJECT (_videoSink), "eos", &videoEos, NULL); +// g_object_get (G_OBJECT (_audioSink), "eos", &audioEos, NULL); + return (bool) (videoEos /*|| audioEos*/); + } + else + return false; +} + +//void VideoImpl::_init() +//{ +// _audioHasNewBuffer = false; +// _videoHasNewBuffer = false; +// +// _terminate = false; +// _seekEnabled = false; +// +// _movieReady=true; +// +// // Stop sleeping the video output. +// _VIDEO_OUT->sleeping(false); +// _AUDIO_OUT->sleeping(false); +//} + + +void VideoImpl::gstNewBufferCallback(GstElement*, int *newBufferCounter) +{ + (*newBufferCounter)++; +} + +VideoImpl::VideoImpl(const QString uri) : +_currentMovie(""), +_bus(NULL), +_pipeline(NULL), +_source(NULL), +//_audioQueue(NULL), +//_audioConvert(NULL), +//_audioResample(NULL), +_videoQueue(NULL), +_videoConvert(NULL), +_videoColorSpace(NULL), +_audioSink(NULL), +_videoSink(NULL), +_width(640), +_height(480), +_data(NULL), +//_audioBufferAdapter(NULL), +_seekEnabled(false), +//_audioNewBufferCounter(0), +_videoNewBufferCounter(0), +_movieReady(false), +_uri(uri) +{ +// addPlug(_VIDEO_OUT = new PlugOut(this, "ImgOut", false)); +// addPlug(_AUDIO_OUT = new PlugOut(this, "AudioOut", false)); +// +// addPlug(_FINISH_OUT = new PlugOut(this, "FinishOut", false)); +// +// QList atLeastOneOfThem; +// atLeastOneOfThem.push_back(_VIDEO_OUT); +// atLeastOneOfThem.push_back(_AUDIO_OUT); +// setPlugAtLeastOneNeeded(atLeastOneOfThem); +// +// addPlug(_RESET_IN = new PlugIn(this, "Reset", false, new ValueType(0, 0, 1))); +// addPlug(_MOVIE_IN = new PlugIn(this, "Movie", false)); +// +// //_settings.add(Property::FILENAME, SETTING_FILENAME)->valueStr(""); +// +// _VIDEO_OUT->sleeping(true); +// _AUDIO_OUT->sleeping(true); +// +// // Crease audio buffer handler. +// _audioBufferAdapter = gst_adapter_new(); +} + +void VideoImpl::unloadMovie() +{ + // Free allocated resources. + freeResources(); + + // Reset flags. +// _audioNewBufferCounter = 0; + _videoNewBufferCounter = 0; + + _terminate = false; + _seekEnabled = false; + + _setReady(false); + + // Unsynch. + // NOTE: I commented this out, it was in Drone, most probably useless but who knows. + // unSynch(); // XXX: I'm not sure why we are doing this... +} + +void VideoImpl::freeResources() +{ + // Free resources. + if (_bus) + { + gst_object_unref (_bus); + _bus = NULL; + } + + if (_pipeline) + { + gst_element_set_state (_pipeline, GST_STATE_NULL); + gst_object_unref (_pipeline); + _pipeline = NULL; + } + + _source = NULL; +// _audioQueue = NULL; +// _audioConvert = NULL; +// _audioResample = NULL; + _videoQueue = NULL; + _videoConvert = NULL; + _videoColorSpace = NULL; + _audioSink = NULL; + _videoSink = NULL; + _padHandlerData = GstPadHandlerData(); + + // Flush buffers in adapter. +// gst_adapter_clear(_audioBufferAdapter); + +} + +void VideoImpl::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) + { + qDebug() << "Seeking at position 0." << endl; + gst_element_seek_simple (_pipeline, GST_FORMAT_TIME, + (GstSeekFlags) (GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), 0); + _setReady(true); + } + else + { + // Just reload movie. + qDebug() << "Reloading the movie" << _seekEnabled << endl; + _currentMovie = ""; + loadMovie(_uri); + } +} + +bool VideoImpl::loadMovie(QString filename) +{ + qDebug() << "Opening movie: " << filename << "."; + + // Free previously allocated structures + unloadMovie(); + + //_firstFrameTime=_formatContext->start_time; + + // Initialize GStreamer. + gst_init (NULL, NULL); + + // Create the elements. + _source = gst_element_factory_make ("uridecodebin", "source"); + +// _audioQueue = gst_element_factory_make ("queue", "aqueue"); +// _audioConvert = gst_element_factory_make ("audioconvert", "aconvert"); +// _audioResample = gst_element_factory_make ("audioresample", "aresample"); +// _audioSink = gst_element_factory_make ("appsink", "asink"); +// + _videoQueue = gst_element_factory_make ("queue", "vqueue"); + _videoColorSpace = gst_element_factory_make ("ffmpegcolorspace", "vcolorspace"); + _videoSink = gst_element_factory_make ("appsink", "vsink"); + + // Prepare handler data. +// _padHandlerData.audioToConnect = _audioQueue; + _padHandlerData.videoToConnect = _videoQueue; + _padHandlerData.videoSink = _videoSink; + //_padHandlerData.audioIsConnected = false; + _padHandlerData.videoIsConnected = false; + +// _newAudioBufferHandlerData.audioSink = _audioSink; +// _newAudioBufferHandlerData.audioBufferAdapter = _audioBufferAdapter; + + // Create the empty pipeline. + _pipeline = gst_pipeline_new ( "video-source-pipeline" ); + + if (!_pipeline || !_source || +// !_audioQueue || !_audioConvert || !_audioResample || !_audioSink || + !_videoQueue || !_videoColorSpace || !_videoSink) { + g_printerr ("Not all elements could be created.\n"); + 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, +// _audioQueue, _audioConvert, _audioResample, _audioSink, + _videoQueue, _videoColorSpace, _videoSink, NULL); + +// if (!gst_element_link_many(_audioQueue, _audioConvert, _audioResample, _audioSink, NULL)) { +// g_printerr ("Audio elements could not be linked.\n"); +// unloadMovie(); +// return false; +// } + + if (!gst_element_link_many (_videoQueue, _videoColorSpace, _videoSink, NULL)) { + g_printerr ("Video elements could not be linked.\n"); + unloadMovie(); + return false; + } + + // Process URI. + gchar* uri = (gchar*) filename.toAscii().constData(); + if (!gst_uri_is_valid(uri)) + { + // Try to convert filename to URI. + GError* error = NULL; + uri = gst_filename_to_uri(uri, &error); + if (error) { + qDebug() << "Filename to URI error: " << error->message << endl; + g_error_free(error); + gst_object_unref (uri); + freeResources(); + return false; + } + } + + // Set URI to be played. + g_object_set (_source, "uri", uri, NULL); + + // Connect to the pad-added signal + g_signal_connect (_source, "pad-added", G_CALLBACK (VideoImpl::gstPadAddedCallback), &_padHandlerData); + + // Configure audio appsink. + // TODO: change from mono to stereo +// gchar* audioCapsText = g_strdup_printf ("audio/x-raw-float,channels=1,rate=%d,signed=(boolean)true,width=%d,depth=%d,endianness=BYTE_ORDER", +// Engine::signalInfo().sampleRate(), (int)(sizeof(Signal_T)*8), (int)(sizeof(Signal_T)*8) ); +// GstCaps* audioCaps = gst_caps_from_string (audioCapsText); +// g_object_set (_audioSink, "emit-signals", TRUE, +// "caps", audioCaps, +//// "max-buffers", 1, // only one buffer (the last) is maintained in the queue +//// "drop", TRUE, // ... other buffers are dropped +// NULL); +// g_signal_connect (_audioSink, "new-buffer", G_CALLBACK (VideoImpl::gstNewAudioBufferCallback), &_newAudioBufferHandlerData); +// gst_caps_unref (audioCaps); +// g_free (audioCapsText); + + // Configure video appsink. +// GstCaps *videoCaps = gst_caps_from_string ("video/x-raw-rgb"); + GstCaps *videoCaps = gst_caps_from_string ("video/x-raw-rgb,format=RGBA,bpp=32,depth=32"); + g_object_set (_videoSink, "emit-signals", TRUE, + "caps", videoCaps, // this sets video caps to "video/x-raw-rgb" + "max-buffers", 1, // only one buffer (the last) is maintained in the queue + "drop", TRUE, // ... other buffers are dropped + NULL); + g_signal_connect (_videoSink, "new-buffer", G_CALLBACK (VideoImpl::gstNewBufferCallback), &_videoNewBufferCounter); + gst_caps_unref (videoCaps); + + // Listen to the bus. + _bus = gst_element_get_bus (_pipeline); + + // Start playing. + if (!_setPlayState(true)) + return false; + + qDebug() << "Pipeline started." << endl; + + //_movieReady = true; + return true; +} + + +void VideoImpl::runVideo() { + +// if (!_VIDEO_OUT->connected()) +// return; + + if (!_preRun()) + return; + + if (_videoNewBufferCounter > 0) { + + // Pull video. + if (!_videoPull()) + { + _setFinished(true); +// _FINISH_OUT->type()->setValue(1.0f); +// _VIDEO_OUT->sleeping(true); + } else +// _VIDEO_OUT->sleeping(false); + + + _videoNewBufferCounter--; + } + + _postRun(); +} + + +//void VideoImpl::runAudio() { +// +// if (!_AUDIO_OUT->connected()) +// return; +// +// if (!_preRun()) +// return; +// +// unsigned int blockByteSize = Engine::signalInfo().blockSize()*sizeof(Signal_T); +// if (gst_adapter_available(_audioBufferAdapter) >= blockByteSize ) +// { +// // Copy block of data to audio output. +// gst_adapter_copy(_audioBufferAdapter, (guint8*)_AUDIO_OUT->type()->data(), 0, blockByteSize); +// gst_adapter_flush (_audioBufferAdapter, blockByteSize); +// +// _AUDIO_OUT->sleeping(false); +// } +// else +// { +// _FINISH_OUT->type()->setValue(1.0f); +// _AUDIO_OUT->sleeping(true); +// } +// +// _postRun(); +//} + +bool VideoImpl::_preRun() +{ + // Check for end-of-stream or terminate. + if (_eos() || _terminate) + { + _setFinished(true); + resetMovie(); + +// _FINISH_OUT->type()->setValue(1.0f); +// _VIDEO_OUT->sleeping(true); +// _AUDIO_OUT->sleeping(true); +// +// if (_audioBufferAdapter != NULL) +// gst_adapter_clear(_audioBufferAdapter); + } + else + _setFinished(false); +// _FINISH_OUT->type()->setValue(0.0f); + +// if (_RESET_IN->type()->boolValue()) +// resetMovie(); + + if (!_movieReady || + !_padHandlerData.isConnected()) + return false; + + return true; +} + +void VideoImpl::_postRun() +{ + // Parse message. + if (_bus != NULL) + { + GstMessage *msg = gst_bus_timed_pop_filtered( + _bus, 0, + (GstMessageType) (GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS)); + + if (msg != NULL) { + GError *err; + gchar *debug_info; + + 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", + GST_OBJECT_NAME (msg->src), err->message); + g_printerr("Debugging information: %s\n", + debug_info ? debug_info : "none"); + g_clear_error(&err); + g_free(debug_info); + + _terminate = true; +// _finish(); + break; + + case GST_MESSAGE_EOS: + g_print("End-Of-Stream reached.\n"); +// _terminate = true; +// _finish(); + break; + + case GST_MESSAGE_STATE_CHANGED: + // We are only interested in state-changed messages from the pipeline. + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (_pipeline)) { + GstState oldState, newState, pendingState; + gst_message_parse_state_changed(msg, &oldState, &newState, + &pendingState); + g_print("Pipeline state for movie %s changed from %s to %s:\n", + _currentMovie.toAscii().constData(), + gst_element_state_get_name(oldState), + gst_element_state_get_name(newState)); + +// if (oldState == GST_STATE_PAUSED && newState == GST_STATE_READY) +// gst_adapter_clear(_audioBufferAdapter); + + if (newState == GST_STATE_PLAYING) { + // Check if seeking is allowed. + gint64 start, end; + GstQuery *query = gst_query_new_seeking (GST_FORMAT_TIME); + if (gst_element_query (_pipeline, query)) + { + gst_query_parse_seeking (query, NULL, (gboolean*)&_seekEnabled, &start, &end); + if (_seekEnabled) + { + g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n", + GST_TIME_ARGS (start), GST_TIME_ARGS (end)); + } + else + { + g_print ("Seeking is DISABLED for this stream.\n"); + } + } + else + { + g_printerr ("Seeking query failed."); + } + + gst_query_unref (query); + } + } + break; + + default: + // We should not reach here. + g_printerr("Unexpected message received.\n"); + break; + } + gst_message_unref(msg); + } + } +} + + +bool VideoImpl::_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) + { + qDebug() << "Unable to set the pipeline to the playing state." << endl; + unloadMovie(); + return false; + } + else + { + _setReady(play); + + return true; + } +} + +void VideoImpl::_setReady(bool ready) +{ + _movieReady = ready; +// _VIDEO_OUT->sleeping(!ready); +// _AUDIO_OUT->sleeping(!ready); +} + +void VideoImpl::_setFinished(bool finished) { + qDebug() << "Clip " << (finished ? "finished" : "not finished") << endl; +} + +void VideoImpl::gstPadAddedCallback(GstElement *src, GstPad *newPad, VideoImpl::GstPadHandlerData* data) { + g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (newPad), GST_ELEMENT_NAME (src)); + bool isAudio = false; + GstPad *sinkPad = NULL; + + // Check the new pad's type. + GstCaps *newPadCaps = gst_pad_get_caps (newPad); + 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)); + if (g_str_has_prefix (newPadType, "audio/x-raw")) + { + sinkPad = gst_element_get_static_pad (data->audioToConnect, "sink"); + isAudio = true; + } + else if (g_str_has_prefix (newPadType, "video/x-raw")) + { + sinkPad = gst_element_get_static_pad (data->videoToConnect, "sink"); + isAudio = false; + } + else + { + g_print (" It has type '%s' which is not raw audio/video. Ignoring.\n", newPadType); + goto exit; + } + + // If our converter is already linked, we have nothing to do here. + if (gst_pad_is_linked (sinkPad)) + { + // Best prefixes. + if (g_str_has_prefix (newPadType, "audio/x-raw-float") || + g_str_has_prefix (newPadType, "video/x-raw-int") ) + { + g_print (" Found a better pad.\n"); + GstPad* oldPad = gst_pad_get_peer(sinkPad); + gst_pad_unlink(oldPad, sinkPad); + g_object_unref(oldPad); + } + else + { + g_print (" We are already linked. Ignoring.\n"); + goto exit; + } + } + + // Attempt the link + if (GST_PAD_LINK_FAILED (gst_pad_link (newPad, sinkPad))) { + g_print (" Type is '%s' but link failed.\n", newPadType); + goto exit; + } else { + g_print (" Link succeeded (type '%s').\n", newPadType); + if (isAudio) + { + data->audioIsConnected = true; + } + else + { + data->videoIsConnected = true; + } + } + +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 VideoImpl::gstNewAudioBufferCallback(GstElement *sink, GstNewAudioBufferHandlerData *data) { +// GstBuffer *buffer = NULL; +// +// // Retrieve the buffer. +// // TODO: we should pull ALL buffers and add them to the adapter +// g_signal_emit_by_name (data->audioSink, "pull-buffer", &buffer); +// +// if (buffer) +// { +// ASSERT_WARNING_MESSAGE( ! GST_BUFFER_IS_DISCONT(buffer), "Discontinuity detected in audio buffer." ); +// +//// int blockSize = 2; +//// int sampleRate = 1; +//// int channels = 0; +//// int width = 0; +//// GstCaps* caps = GST_BUFFER_CAPS(buffer); +//// GstStructure *capsStruct = gst_caps_get_structure (caps, 0); +//// +//// gst_structure_get_int(capsStruct, "rate", &sampleRate); +//// gst_structure_get_int(capsStruct, "channels", &channels); +//// gst_structure_get_int(capsStruct, "width", &width); +// +//// qDebug() << "rate = " << sampleRate << " channels = " << channels << " width = " << width << endl; +//// unsigned int blockByteSize = Engine::signalInfo().blockSize() * sizeof(Signal_T); +// +//// qDebug() << "bufsize: "<< GST_BUFFER_SIZE(buffer) << +//// " / adaptersize: " << gst_adapter_available(data->audioBufferAdapter) << endl; +// +// // Add buffer to the adapter. +// gst_adapter_push(data->audioBufferAdapter, buffer); +// // qDebug() << " .. after push = : "<< gst_adapter_available(_audioBufferAdapter); +// +// // NOTE: no need to unref the buffer here because the buffer was given away with the +// // call to gst_adapter_push() +// //gst_buffer_unref (buffer); +// } +//} + +void VideoImpl::internalPrePlay() +{ + // Start/resume playback. + _setPlayState(true); +} + +void VideoImpl::internalPostPlay() +{ + // Pause playback. + _setPlayState(false); +} + + + + + diff --git a/VideoImpl.h b/VideoImpl.h new file mode 100644 index 0000000..2ba23ec --- /dev/null +++ b/VideoImpl.h @@ -0,0 +1,179 @@ +/* + * VideoImpl.h + * + * (c) 2013 Sofian Audry -- info(@)sofianaudry(.)com + * (c) 2013 Alexandre Quessy -- alexandre(@)quessy(.)net + * (c) 2012 Jean-Sebastien Senecal + * (c) 2004 Mathieu Guindon, Julien Keable + * Based on code from Drone http://github.com/sofian/drone + * Based on code from the GStreamer Tutorials http://docs.gstreamer.com/display/GstSDK/Tutorials + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef VIDEO_IMPL_H_ +#define VIDEO_IMPL_H_ + +#include +#include +#include +#if __APPLE__ +#include +#else +#include +#endif +#include + +/** + * Private declaration of the video player. + * This is done this way so that GStreamer header don't need to be + * included in the whole project. (just in this file) + */ +class VideoImpl +{ +public: + VideoImpl(const QString uri); + ~VideoImpl(); + +// void setUri(const QString uri); + static bool hasVideoSupport(); + void build(); + int getWidth() const; + int getHeight() const; + const uchar* getBits() const; + + void runVideo(); +// void runAudio(); + +protected: + bool loadMovie(QString filename); + void unloadMovie(); + void freeResources(); + void resetMovie(); + + void internalPrePlay(); + void internalPostPlay(); + +private: + bool _videoPull(); + bool _eos() const; +// void _finish(); +// void _init(); + + bool _preRun(); + void _postRun(); + bool _setPlayState(bool play); + void _setReady(bool ready); + void _setFinished(bool finished); + +public: + // GStreamer callbacks. + + struct GstPadHandlerData { + GstElement* audioToConnect; + GstElement* videoToConnect; + GstElement* videoSink; + bool audioIsConnected; + bool videoIsConnected; + + GstPadHandlerData() : + audioToConnect(NULL), videoToConnect(NULL), videoSink(NULL), + audioIsConnected(false), videoIsConnected(false) + {} + + bool isConnected() const { + return (/*audioIsConnected && */videoIsConnected); + } + }; + +// struct GstNewAudioBufferHandlerData { +// GstElement* audioSink; +// GstAdapter* audioBufferAdapter; +// GstNewAudioBufferHandlerData() : audioSink(NULL), audioBufferAdapter(NULL) {} +// }; + + // GStreamer callback that simply sets the #newBuffer# flag to point to TRUE. + static void gstNewBufferCallback(GstElement *sink, int *newBufferCounter); + +// static void gstNewAudioBufferCallback(GstElement *sink, GstNewAudioBufferHandlerData *data); + + // 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, VideoImpl::GstPadHandlerData* data); + +private: +// PlugOut *_VIDEO_OUT; +// PlugOut *_AUDIO_OUT; +// PlugOut *_FINISH_OUT; +// PlugIn *_RESET_IN; +// PlugIn *_MOVIE_IN; +// +// VideoRGBAType *_imageOut; + + //locals + QString _currentMovie; + + // gstreamer + GstBus *_bus; + GstElement *_pipeline; + GstElement *_source; + GstElement *_audioQueue; + GstElement *_audioConvert; + GstElement *_audioResample; + GstElement *_videoQueue; + GstElement *_videoConvert; + GstElement *_videoColorSpace; + GstElement *_audioSink; + GstElement *_videoSink; + +// GstAdapter *_audioBufferAdapter; + + GstPadHandlerData _padHandlerData; +// GstNewAudioBufferHandlerData _newAudioBufferHandlerData; + + int _width; + int _height; + uchar *_data; + + bool _seekEnabled; + + int _audioNewBufferCounter; + int _videoNewBufferCounter; + + bool _terminate; + bool _movieReady; + +private: + QString _uri; +}; + +//! Fast converts 24-bits color to 32 bits (alpha is set to specified alpha value). +// Based on: http://stackoverflow.com/questions/7069090/convert-rgb-to-rgba-in-c +inline void convert24to32(unsigned char *dst, const unsigned char *src, size_t size, unsigned char alpha=0xff) { + Q_ASSERT(dst != NULL); + Q_ASSERT(src != NULL); + if (size==0) return; + uint32_t alphaMask = ((uint32_t)alpha) << 24; + // Copy by 4 byte blocks. + for (size_t i=size; --i; dst+=4, src+=3) + { + *(uint32_t*)(void*)dst = (*(const uint32_t*)(const void*)src) | alphaMask; + } + // Copy remaining bytes. + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; +} + +#endif /* ifndef */ diff --git a/build.sh b/build.sh index c80254b..949c782 100755 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ if [[ $unamestr == "Darwin" ]]; then #export QMAKESPEC=macx-xcode PATH=$PATH:/Users/aalex/Qt5.2.1/5.2.1/clang_64/bin qmake5=/Users/aalex/Qt5.2.1/5.2.1/clang_64/bin/qmake - $qmake5 + $qmake5 -spec macx-llvm elif [[ $unamestr == "Linux" ]]; then qmake-qt4 fi diff --git a/mapmap.pro b/mapmap.pro index 66f5aaa..228e4de 100644 --- a/mapmap.pro +++ b/mapmap.pro @@ -18,12 +18,14 @@ HEADERS = \ OscInterface.h \ OscReceiver.h \ OutputGLWindow.h \ + Paint.h \ ProjectReader.h \ ProjectWriter.h \ Shape.h \ SourceGLCanvas.h \ UidAllocator.h \ - Util.h + Util.h \ + VideoImpl.h SOURCES = \ DestinationGLCanvas.cpp \ @@ -43,6 +45,7 @@ SOURCES = \ SourceGLCanvas.cpp \ UidAllocator.cpp \ Util.cpp \ + VideoImpl.cpp \ main.cpp QT += gui opengl xml @@ -79,8 +82,10 @@ unix:!mac { # -lgmodule-2.0 \ # -lgobject-2.0 \ -lgthread-2.0 \ - -lGLU \ -lGL \ + -lGLU \ + -llo \ + -lpthread \ -lX11 \ -lGLEW } @@ -98,6 +103,42 @@ mac { QMAKE_CXXFLAGS += -D__MACOSX_CORE__ QMAKE_CXXFLAGS += -stdlib=libstdc++ - # -lGLEW + INCLUDEPATH += /Library/Frameworks/GStreamer.framework/Headers + # LIBS+=-lavformat -lavcodec -lavutil -lswscale -lz + LIBS += -F /Library/Frameworks/ -framework GStreamer + # QMAKE_LFLAGS+=-read_only_relocs suppress + + # LIBS += -lgstreamer-0.10 \ + # -lgstapp-0.10 \ + # -lgstvideo-0.10 \ + # -lglib-2.0 \ + # -lgobject-2.0 + LIBS += -framework OpenGL -framework GLUT + QMAKE_CXXFLAGS += -D__MACOSX_CORE__ +} + +# Windows-specific: +win32 { + DEFINES += WIN32 + INCLUDEPATH += \ + C:/gstreamer/include \ + C:/gstreamer/include/libxml2 \ + C:/gstreamer/include/glib-2.0 \ + C:/gstreamer/lib/glib-2.0/include \ + C:/gstreamer/include/gstreamer-0.10 + LIBS += -L"C:/gstreamer/lib" \ + -L"C:/gstreamer/bin" \ + -lgstreamer-0.10 \ + -lglib-2.0 \ + -lgmodule-2.0 \ + -lgobject-2.0 \ + -lgthread-2.0 \ + -lgstinterfaces-0.10 \ + -lopengl32 \ + -lglu32 \ + -lglew32 + # Add console to the CONFIG to see debug messages printed in + # the console on Windows + CONFIG += console } diff --git a/unused.h b/unused.h new file mode 100644 index 0000000..ba88459 --- /dev/null +++ b/unused.h @@ -0,0 +1,7 @@ +#ifndef __mapmap_unused_h__ +#define __mapmap_unused_h__ + +#define UNUSED(x) (void)(x) + +#endif // ifndef +