From 1796d55bea56a7b971f32937a8fe210c476f1fc1 Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Tue, 2 Oct 2012 14:07:12 +0000 Subject: [PATCH] From Stephan Huber, OSX and iOS Video support via a QTKit plugin from OSX 10.7 and before, and an AVFoundation plugin for iOS and OSX10.8 and later. --- CMakeLists.txt | 3 + CMakeModules/FindAVFoundation.cmake | 41 + CMakeModules/FindCoreMedia.cmake | 23 + CMakeModules/FindQuartzCore.cmake | 23 + examples/CMakeLists.txt | 1 + examples/osgmultiplemovies/CMakeLists.txt | 19 + .../osgmultiplemovies/osgmultiplemovies.cpp | 703 ++++++++++++++++++ include/osg/Image | 21 + include/osg/ImageStream | 6 +- src/osg/Image.cpp | 36 +- src/osg/Referenced.cpp | 3 +- src/osgDB/CMakeLists.txt | 3 + src/osgDB/Registry.cpp | 8 + src/osgPlugins/CMakeLists.txt | 4 + src/osgPlugins/QTKit/CMakeLists.txt | 10 +- src/osgPlugins/QTKit/OSXCoreVideoAdapter.h | 50 ++ src/osgPlugins/QTKit/OSXCoreVideoAdapter.mm | 127 ++++ src/osgPlugins/QTKit/OSXCoreVideoTexture.cpp | 147 ++++ src/osgPlugins/QTKit/OSXCoreVideoTexture.h | 80 ++ src/osgPlugins/QTKit/OSXQTKitVideo.h | 99 +++ src/osgPlugins/QTKit/OSXQTKitVideo.mm | 370 +++++++++ src/osgPlugins/QTKit/ReaderWriterQTKit.cpp | 150 ++++ src/osgPlugins/QTKit/VideoFrameDispatcher.cpp | 187 +++++ src/osgPlugins/QTKit/VideoFrameDispatcher.h | 108 +++ src/osgPlugins/avfoundation/CMakeLists.txt | 17 + .../avfoundation/OSXAVFoundationVideo.h | 112 +++ .../avfoundation/OSXAVFoundationVideo.mm | 514 +++++++++++++ .../OSXAvFoundationCoreVideoTexture.cpp | 152 ++++ .../OSXAvFoundationCoreVideoTexture.h | 76 ++ .../avfoundation/ReaderWriterAVFoundation.cpp | 117 +++ 30 files changed, 3202 insertions(+), 8 deletions(-) create mode 100644 CMakeModules/FindAVFoundation.cmake create mode 100644 CMakeModules/FindCoreMedia.cmake create mode 100644 CMakeModules/FindQuartzCore.cmake create mode 100644 examples/osgmultiplemovies/CMakeLists.txt create mode 100644 examples/osgmultiplemovies/osgmultiplemovies.cpp create mode 100644 src/osgPlugins/QTKit/OSXCoreVideoAdapter.h create mode 100644 src/osgPlugins/QTKit/OSXCoreVideoAdapter.mm create mode 100644 src/osgPlugins/QTKit/OSXCoreVideoTexture.cpp create mode 100644 src/osgPlugins/QTKit/OSXCoreVideoTexture.h create mode 100644 src/osgPlugins/QTKit/OSXQTKitVideo.h create mode 100644 src/osgPlugins/QTKit/OSXQTKitVideo.mm create mode 100644 src/osgPlugins/QTKit/ReaderWriterQTKit.cpp create mode 100644 src/osgPlugins/QTKit/VideoFrameDispatcher.cpp create mode 100644 src/osgPlugins/QTKit/VideoFrameDispatcher.h create mode 100644 src/osgPlugins/avfoundation/CMakeLists.txt create mode 100644 src/osgPlugins/avfoundation/OSXAVFoundationVideo.h create mode 100644 src/osgPlugins/avfoundation/OSXAVFoundationVideo.mm create mode 100644 src/osgPlugins/avfoundation/OSXAvFoundationCoreVideoTexture.cpp create mode 100644 src/osgPlugins/avfoundation/OSXAvFoundationCoreVideoTexture.h create mode 100644 src/osgPlugins/avfoundation/ReaderWriterAVFoundation.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e8dc63420..c11798815 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -593,6 +593,9 @@ IF(NOT ANDROID) FIND_PACKAGE(QuickTime) FIND_PACKAGE(QTKit) FIND_PACKAGE(CoreVideo) + FIND_PACKAGE(CoreMedia) + FIND_PACKAGE(QuartzCore) + FIND_PACKAGE(AVFoundation) ENDIF() ENDIF() diff --git a/CMakeModules/FindAVFoundation.cmake b/CMakeModules/FindAVFoundation.cmake new file mode 100644 index 000000000..a5b8ada81 --- /dev/null +++ b/CMakeModules/FindAVFoundation.cmake @@ -0,0 +1,41 @@ +# Locate Apple AVFoundation (next-generation QTKit) +# This module defines +# AV_FOUNDATION_LIBRARY +# AV_FOUNDATION_FOUND, if false, do not try to link to gdal +# AV_FOUNDATION_INCLUDE_DIR, where to find the headers +# +# $AV_FOUNDATION_DIR is an environment variable that would +# correspond to the ./configure --prefix=$AV_FOUNDATION_DIR +# +# Created by Stephan Maximilian Huber + +# QTKit on OS X looks different than QTKit for Windows, +# so I am going to case the two. + +IF(APPLE) + FIND_PATH(AV_FOUNDATION_INCLUDE_DIR AVFoundation/AVFoundation.h) + FIND_LIBRARY(AV_FOUNDATION_LIBRARY AVFoundation) +ENDIF() + +SET(AV_FOUNDATION_FOUND "NO") +IF(AV_FOUNDATION_LIBRARY AND AV_FOUNDATION_INCLUDE_DIR) + SET(AV_FOUNDATION_FOUND "YES") +ENDIF() + +IF(OSG_BUILD_PLATFORM_IPHONE OR OSG_BUILD_PLATFORM_IPHONE_SIMULATOR) + # TODO, AVFoundation exists ON iOS, too + SET(AV_FOUNDATION_FOUND "NO") +ENDIF() + +IF(APPLE) + # AVFoundation exists since 10.7, but only 10.8 has all features necessary for OSG + # so check the SDK-setting + + IF(${OSG_OSX_SDK_NAME} STREQUAL "macosx10.8") + # nothing special here ;-) + ELSE() + MESSAGE("AVFoundation disabled for SDK < 10.8") + SET(AV_FOUNDATION_FOUND "NO") + ENDIF() +ENDIF() + diff --git a/CMakeModules/FindCoreMedia.cmake b/CMakeModules/FindCoreMedia.cmake new file mode 100644 index 000000000..3e7eb629b --- /dev/null +++ b/CMakeModules/FindCoreMedia.cmake @@ -0,0 +1,23 @@ +# Locate Apple CoreMedia +# This module defines +# COREMEDIA_LIBRARY +# COREMEDIA_FOUND, if false, do not try to link to gdal +# COREMEDIA_INCLUDE_DIR, where to find the headers +# +# $COREMEDIA_DIR is an environment variable that would +# correspond to the ./configure --prefix=$COREMEDIA_DIR +# +# Created by Stephan Maximilian Huber. + + +IF(APPLE) + FIND_PATH(COREMEDIA_INCLUDE_DIR CoreMedia/CoreMedia.h) + FIND_LIBRARY(COREMEDIA_LIBRARY CoreMedia) +ENDIF() + + +SET(COREMEDIA_FOUND "NO") +IF(COREMEDIA_LIBRARY AND COREMEDIA_INCLUDE_DIR) + SET(COREMEDIA_FOUND "YES") +ENDIF() + diff --git a/CMakeModules/FindQuartzCore.cmake b/CMakeModules/FindQuartzCore.cmake new file mode 100644 index 000000000..cb1c35303 --- /dev/null +++ b/CMakeModules/FindQuartzCore.cmake @@ -0,0 +1,23 @@ +# Locate Apple QuartzCore +# This module defines +# QUARTZCORE_LIBRARY +# QUARTZCORE_FOUND, if false, do not try to link to QUARTZCORE +# QUARTZCORE_INCLUDE_DIR, where to find the headers +# +# $QUARTZCORE_DIR is an environment variable that would +# correspond to the ./configure --prefix=$QUARTZCORE_DIR +# +# Created by Stephan Maximilian Huber. + + +IF(APPLE) + FIND_PATH(QUARTZCORE_INCLUDE_DIR QuartzCore/QuartzCore.h) + FIND_LIBRARY(QUARTZCORE_LIBRARY QuartzCore) +ENDIF() + + +SET(QUARTZCORE_FOUND "NO") +IF(QUARTZCORE_LIBRARY AND QUARTZCORE_INCLUDE_DIR) + SET(QUARTZCORE_FOUND "YES") +ENDIF() + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3aea10cf1..9a84b2e9e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -68,6 +68,7 @@ IF(DYNAMIC_OPENSCENEGRAPH) ADD_SUBDIRECTORY(osggraphicscost) ADD_SUBDIRECTORY(osgmanipulator) ADD_SUBDIRECTORY(osgmovie) + ADD_SUBDIRECTORY(osgmultiplemovies) ADD_SUBDIRECTORY(osgmultiplerendertargets) ADD_SUBDIRECTORY(osgmultitexture) ADD_SUBDIRECTORY(osgmultitexturecontrol) diff --git a/examples/osgmultiplemovies/CMakeLists.txt b/examples/osgmultiplemovies/CMakeLists.txt new file mode 100644 index 000000000..f1b00005b --- /dev/null +++ b/examples/osgmultiplemovies/CMakeLists.txt @@ -0,0 +1,19 @@ +# INCLUDE_DIRECTORIES( ${OPENAL_INCLUDE_DIR} ) +# SET(TARGET_EXTERNAL_LIBRARIES ${OPENAL_LIBRARY} alut) + +IF (SDL_FOUND) + SET(TARGET_EXTERNAL_LIBRARIES ${SDL_LIBRARY} ) + INCLUDE_DIRECTORIES(${SDL_INCLUDE_DIR} ) + ADD_DEFINITIONS(-DUSE_SDL) + IF (MINGW) + SET(TARGET_EXTERNAL_LIBRARIES ${TARGET_EXTERNAL_LIBRARIES} winmm dinput ddraw dxguid) + ENDIF() +ENDIF(SDL_FOUND) + +SET(TARGET_SRC osgmultiplemovies.cpp ) +SET(TARGET_ADDED_LIBRARIES osgGA ) + + + +#### end var setup ### +SETUP_EXAMPLE(osgmultiplemovies) diff --git a/examples/osgmultiplemovies/osgmultiplemovies.cpp b/examples/osgmultiplemovies/osgmultiplemovies.cpp new file mode 100644 index 000000000..eb648cdba --- /dev/null +++ b/examples/osgmultiplemovies/osgmultiplemovies.cpp @@ -0,0 +1,703 @@ +/* OpenSceneGraph example, osgmovie. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +class ImageStreamStateCallback : public osg::Drawable::UpdateCallback { +public: + ImageStreamStateCallback(osgText::Text* text, osg::ImageStream* is) + : osg::Drawable::UpdateCallback() + , _text(text) + , _imageStream(is) + , _fps(0) + , _lastData(NULL) + , _lastDataTimeStamp(0) + { + } + + void setImageStream(osg::ImageStream* is) { _imageStream = is; } + + virtual void update(osg::NodeVisitor* nv, osg::Drawable*) + { + if (_text.valid() && _imageStream.valid()) + { + if (_imageStream->data() != _lastData) + { + double dt = nv->getFrameStamp()->getReferenceTime() - _lastDataTimeStamp; + + _fps = 0.9 * _fps + 0.1 * (1 / dt); + _fps = osg::round(10 * _fps) / 10.0; + + _lastDataTimeStamp = nv->getFrameStamp()->getReferenceTime(); + _lastData = _imageStream->data(); + } + + std::ostringstream ss; + ss << _imageStream->s() << "x" << _imageStream->t() << " | " << _fps << "fps"; + ss << " | len: " << osg::round(_imageStream->getLength()*10) / 10.0; + ss << " | cur: " << osg::round(_imageStream->getCurrentTime()*10) / 10.0; + if (_imageStream->getStatus() == osg::ImageStream::PLAYING) + { + ss << " | playing"; + } + else + { + ss << " | paused"; + _fps = 0; + } + if (_imageStream->getLoopingMode() == osg::ImageStream::LOOPING) { + ss << " | looping"; + } + else + { + ss << " | don't loop"; + } + _text->setText(ss.str()); + } + } + + +private: + osg::observer_ptr _text; + osg::observer_ptr _imageStream; + float _fps; + unsigned char* _lastData; + double _lastDataTimeStamp; + +}; + +class MovieEventHandler : public osgGA::GUIEventHandler +{ +public: + + MovieEventHandler() + : osgGA::GUIEventHandler() + , _currentImageStream() + , _currentGeometry() + { + } + + + virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor* nv); + + virtual void getUsage(osg::ApplicationUsage& usage) const; + +protected: + + void setColor(osg::Geometry* geo, const osg::Vec4& color) + { + if (!geo) + return; + + osg::Vec4Array* c = dynamic_cast(geo->getColorArray()); + if (c) (*c)[0] = color; + geo->dirtyDisplayList(); + c->dirty(); + } + + virtual ~MovieEventHandler() {} + + osg::observer_ptr _currentImageStream; + osg::observer_ptr _currentGeometry; + +}; + + + + + + +bool MovieEventHandler::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor* nv) +{ + switch(ea.getEventType()) + { + case(osgGA::GUIEventAdapter::MOVE): + { + if(_currentImageStream.valid() && (ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_SHIFT)) + { + float scalar = (ea.getXnormalized()+1) / 2.0; + _currentImageStream->seek(scalar * _currentImageStream->getLength()); + } + } + break; + + case(osgGA::GUIEventAdapter::RELEASE): + { + + osgViewer::View* view = dynamic_cast(&aa); + osgUtil::LineSegmentIntersector::Intersections intersections; + bool foundIntersection = view==0 ? false : view->computeIntersections(ea.getX(), ea.getY(), intersections); + + if (foundIntersection) + { + // use the nearest intersection + const osgUtil::LineSegmentIntersector::Intersection& intersection = *(intersections.begin()); + osg::Drawable* drawable = intersection.drawable.get(); + osg::Geometry* geometry = drawable ? drawable->asGeometry() : 0; + + if (geometry) { + osg::Texture* tex = geometry->getStateSet() ? dynamic_cast(geometry->getStateSet()->getTextureAttribute(0, osg::StateAttribute::TEXTURE)) : NULL; + if (tex) { + osg::ImageStream* is = dynamic_cast(tex->getImage(0)); + if (is) + { + setColor(_currentGeometry.get(), osg::Vec4(0.7, 0.7, 0.7, 1.0)); + _currentGeometry = geometry; + setColor(_currentGeometry.get(), osg::Vec4(1,1,1,1)); + _currentImageStream = is; + + if (is->getStatus() == osg::ImageStream::PLAYING) + { + is->pause(); + } + else + { + is->play(); + } + + } + } + } + } + + break; + } + case(osgGA::GUIEventAdapter::KEYDOWN): + { + if (!_currentImageStream.valid()) + return false; + + if (ea.getKey()=='p') + { + osg::ImageStream::StreamStatus playToggle = _currentImageStream->getStatus(); + if (playToggle != osg::ImageStream::PLAYING) + { + std::cout<< _currentImageStream.get() << " Play"<play(); + } + else + { + // playing, so pause + std::cout<< _currentImageStream.get() << " Pause"<pause(); + } + return true; + } + else if (ea.getKey()=='r') + { + std::cout<< _currentImageStream.get() << " Restart"<rewind(); + _currentImageStream->play(); + return true; + } + else if (ea.getKey()=='>') + { + std::cout << _currentImageStream.get() << " Seeking"<seek(_currentImageStream->getCurrentTime() + 1.0); + + return true; + } + else if (ea.getKey()=='L') + { + if ( _currentImageStream->getLoopingMode() == osg::ImageStream::LOOPING) + { + std::cout<< _currentImageStream.get() << " Toggle Looping Off"<setLoopingMode( osg::ImageStream::NO_LOOPING ); + } + else + { + std::cout<< _currentImageStream.get() << " Toggle Looping On"<setLoopingMode( osg::ImageStream::LOOPING ); + } + return true; + } + else if (ea.getKey()=='+') + { + double tm = _currentImageStream->getTimeMultiplier(); + tm += 0.1; + _currentImageStream->setTimeMultiplier(tm); + std::cout << _currentImageStream.get() << " Increase speed rate "<< _currentImageStream->getTimeMultiplier() << std::endl; + + return true; + } + else if (ea.getKey()=='-') + { + double tm = _currentImageStream->getTimeMultiplier(); + tm -= 0.1; + _currentImageStream->setTimeMultiplier(tm); + std::cout << _currentImageStream.get() << " Decrease speed rate "<< _currentImageStream->getTimeMultiplier() << std::endl; + + return true; + } + else if (ea.getKey()=='o') + { + std::cout<< _currentImageStream.get() << " Frame rate "<< _currentImageStream->getFrameRate() <","Advance the current movie using seek"); +} + + +static osgDB::DirectoryContents getSuitableFiles(osg::ArgumentParser& arguments) +{ + osgDB::DirectoryContents files; + for(int i=1; igetTextureTarget() == GL_TEXTURE_2D) ? 1 : img->s(); + float b = (_tex->getTextureTarget() == GL_TEXTURE_2D) ? 1 : img->t(); + + /* + if (img->getOrigin() == osg::Image::TOP_LEFT) + std::swap(t, b); + */ + + osg::Vec2Array* tex_coords = dynamic_cast(_geo->getTexCoordArray(0)); + if (tex_coords) { + + (*tex_coords)[0].set(l,t); + (*tex_coords)[1].set(l,b); + (*tex_coords)[2].set(r,b); + (*tex_coords)[3].set(r,t); + tex_coords->dirty(); + _geo->dirtyDisplayList(); + } + } + } + +private: + osg::observer_ptr _tex; + osg::observer_ptr _geo; +}; + +static osg::Node* readImageStream(const std::string& file_name, osg::Vec3& p, float desired_height, osgDB::Options* options) +{ + osg::ref_ptr obj = osgDB::readObjectFile(file_name, options); + osg::ref_ptr tex = dynamic_cast(obj.get()); + osg::Geometry* geo(NULL); + float w(0); + + if (!tex) + { + osg::ref_ptr img_stream = dynamic_cast(obj.get()); + + // try readImageFile if readObjectFile failed + if (!img_stream) + { + img_stream = dynamic_cast(osgDB::readImageFile(file_name, options)); + } + + if (img_stream) + { + tex = new osg::Texture2D(img_stream); + tex->setResizeNonPowerOfTwoHint(false); + + } + } + + // create textured quad + if(tex) + { + osg::Geode* geode = new osg::Geode(); + + osg::ref_ptr img = dynamic_cast(tex->getImage(0)); + if (img) + { + w = (img->t() > 0) ? img->s() * desired_height / img->t() : 0; + + osgText::Text* text = new osgText::Text(); + text->setFont("arial.ttf"); + text->setDataVariance(osg::Object::DYNAMIC); + text->setUpdateCallback(new ImageStreamStateCallback(text, img)); + text->setCharacterSize(24); + text->setPosition(p + osg::Vec3(10,-10,10)); + text->setAxisAlignment(osgText::TextBase::XZ_PLANE); + geode->addDrawable (text); + } + + if (w == 0) + { + // hmm, imagestream with no width? + w = desired_height * 16 / 9.0f; + } + float tex_s = (tex->getTextureTarget() == GL_TEXTURE_2D) ? 1 : img->s(); + float tex_t = (tex->getTextureTarget() == GL_TEXTURE_2D) ? 1 : img->t(); + + if (img->getOrigin() == osg::Image::TOP_LEFT) + geo = osg::createTexturedQuadGeometry(p, osg::Vec3(w, 0, 0), osg::Vec3(0, 0, desired_height), 0, tex_t, tex_s, 0); + else + geo = osg::createTexturedQuadGeometry(p, osg::Vec3(w, 0, 0), osg::Vec3(0, 0, desired_height), 0, 0, tex_s, tex_t); + + geode->addDrawable(geo); + + geo->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex); + + osg::Vec4Array* colors = new osg::Vec4Array(); + colors->push_back(osg::Vec4(0.7, 0.7, 0.7, 1)); + + geo->setColorArray(colors); + geo->setColorBinding(osg::Geometry::BIND_OVERALL); + + p[0] += w + 10; + + img->addDimensionsChangedCallback(new MyDimensionsChangedCallback(tex, geo)); + + return geode; + } + else + { + std::cout << "could not read file from " << file_name << std::endl; + return NULL; + } + + return NULL; +} + + +class ReplaceTextureVisitor : public osg::NodeVisitor { +public: + ReplaceTextureVisitor(osg::Texture* tex) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , _tex(tex) + { + } + + virtual void apply(osg::Geode& geode) + { + apply(geode.getStateSet()); + for(unsigned int i = 0; i < geode.getNumDrawables(); ++i) + { + osg::Drawable* drawable = geode.getDrawable(i); + + apply(drawable->getStateSet()); + ImageStreamStateCallback* cb = dynamic_cast(drawable->getUpdateCallback()); + if (cb) + cb->setImageStream(dynamic_cast(_tex->getImage(0))); + + } + + osg::NodeVisitor::apply(geode); + } + + void apply(osg::StateSet* ss) + { + if (ss && ss->getTextureAttribute(0, osg::StateAttribute::TEXTURE)) + ss->setTextureAttribute(0, _tex); + } +private: + osg::ref_ptr _tex; +}; + + +class SlideShowEventHandler : public osgGA::GUIEventHandler { +public: + SlideShowEventHandler(osg::Node* node, const osgDB::DirectoryContents& files,osgDB::ReaderWriter::Options* options) + : osgGA::GUIEventHandler() + , _node(node) + , _files(files) + , _options(options) + , _currentFile(-1) + { + loadSlide(_currentFile); + } + + bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor* nv) + { + switch(ea.getEventType()) + { + case(osgGA::GUIEventAdapter::KEYDOWN): + { + if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Left) + loadSlide(_currentFile - 1); + else if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Right) + loadSlide(_currentFile + 1); + else + return false; + + return true; + } + break; + default: + break; + } + + return false; + } + +private: + + void loadSlide(int new_ndx) + { + if (new_ndx == _currentFile) + return; + + _currentFile = new_ndx; + if (_currentFile < 0) + _currentFile = _files.size() - 1; + else if (_currentFile >= _files.size()) + _currentFile = 0; + + osg::ref_ptr obj = osgDB::readRefObjectFile(_files[_currentFile], _options); + osg::ref_ptr tex = dynamic_cast(obj.get()); + if (!tex) { + osg::ref_ptr stream = dynamic_cast(obj.get()); + if (!stream) + stream = dynamic_cast(osgDB::readImageFile(_files[_currentFile], _options)); + + if (stream) + { + tex = new osg::Texture2D(stream); + tex->setResizeNonPowerOfTwoHint(false); + } + } + if (tex) { + osg::ref_ptr stream = dynamic_cast(tex->getImage(0)); + if (stream) + stream->play(); + ReplaceTextureVisitor v(tex); + _node->accept(v); + } + } + + osg::ref_ptr _node; + osgDB::DirectoryContents _files; + osg::ref_ptr _options; + int _currentFile; +}; + + +int main(int argc, char** argv) +{ + /* + std::string plugin_to_use = "AVFoundation"; // "QTKit"; + + osgDB::Registry::instance()->addFileExtensionAlias("mov", plugin_to_use); + osgDB::Registry::instance()->addFileExtensionAlias("mp4", plugin_to_use); + osgDB::Registry::instance()->addFileExtensionAlias("m4v", plugin_to_use); + */ + + // use an ArgumentParser object to manage the program arguments. + osg::ArgumentParser arguments(&argc,argv); + + // set up the usage document, in case we need to print out how to use this program. + arguments.getApplicationUsage()->setApplicationName(arguments.getApplicationName()); + arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" example demonstrates the use of ImageStream for rendering movies as textures."); + arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] filename ..."); + arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display this information"); + arguments.getApplicationUsage()->addCommandLineOption("--disableCoreVideo","disable CoreVideo (QTKit+AVFoundation plugin)"); + arguments.getApplicationUsage()->addCommandLineOption("--disableMultiThreadedFrameDispatching","disable frame dispatching via multiple threads (QTKit+AVFoundation plugin)"); + arguments.getApplicationUsage()->addCommandLineOption("--maxVideos [numVideos]","max videos to open from a folder"); + arguments.getApplicationUsage()->addCommandLineOption("--slideShow","present movies in a slide-show"); + + unsigned int max_videos(10); + bool slide_show = false; + std::string options_str(""); + + if (arguments.find("--slideShow") > 0) { + slide_show = true; + } + + if (arguments.find("--disableMultiThreadedFrameDispatching") > 0) { + options_str += " disableMultiThreadedFrameDispatching"; + } + + if (arguments.find("--disableCoreVideo") > 0) { + options_str += " disableCoreVideo"; + } + + if (int ndx = arguments.find("--numFrameDispatchThreads") > 0) + { + options_str += std::string(" numFrameDispatchThreads=") + arguments[ndx+1]; + } + if (int ndx = arguments.find("--maxVideos") > 0) + { + if (arguments.isNumber(ndx+1)) max_videos = atoi(arguments[ndx+1]); + } + + + // construct the viewer. + osgViewer::Viewer viewer(arguments); + + if (arguments.argc()<=1) + { + arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION); + return 1; + } + + + // if user request help write it out to cout. + if (arguments.read("-h") || arguments.read("--help")) + { + arguments.getApplicationUsage()->write(std::cout); + return 1; + } + + + osg::ref_ptr group = new osg::Group; + + osg::StateSet* stateset = group->getOrCreateStateSet(); + stateset->setMode(GL_LIGHTING,osg::StateAttribute::OFF); + + + osg::Vec3 pos(0.0f,0.0f,0.0f); + static const float desired_height = 768.0f; + + osgDB::DirectoryContents files = getSuitableFiles(arguments); + osgGA::GUIEventHandler* movie_event_handler(NULL); + + osg::ref_ptr options = new osgDB::ReaderWriter::Options(options_str); + + if (slide_show) + { + osg::Node* node = readImageStream(files[0], pos, desired_height, options); + group->addChild(node); + movie_event_handler = new SlideShowEventHandler(node, files, options); + } + else + { + movie_event_handler = new MovieEventHandler(); + + unsigned int num_files_per_row = std::max(osg::round(sqrt(std::min(max_videos, files.size()))), 1.0); + static const float new_row_at = num_files_per_row * desired_height * 16 / 9.0; + + unsigned int num_videos = 0; + for(osgDB::DirectoryContents::iterator i = files.begin(); (i != files.end()) && (num_videos < max_videos); ++i) + { + osg::Node* node = readImageStream(*i, pos, desired_height, options); + if (node) + group->addChild(node); + + if (pos[0] > new_row_at) + { + pos[0] = 0; + pos[2] += desired_height +10; + } + num_videos++; + } + } + + + // set the scene to render + viewer.setSceneData(group.get()); + + if (viewer.getSceneData()==0) + { + arguments.getApplicationUsage()->write(std::cout); + return 1; + } + + + viewer.addEventHandler( movie_event_handler ); + viewer.addEventHandler( new osgViewer::StatsHandler ); + viewer.addEventHandler( new osgViewer::ToggleSyncToVBlankHandler()); + viewer.addEventHandler( new osgGA::StateSetManipulator( viewer.getCamera()->getOrCreateStateSet() ) ); + viewer.addEventHandler( new osgViewer::WindowSizeHandler ); + + // add the record camera path handler + viewer.addEventHandler(new osgViewer::RecordCameraPathHandler); + + // report any errors if they have occurred when parsing the program arguments. + if (arguments.errors()) + { + arguments.writeErrorMessages(std::cout); + return 1; + } + + // create the windows and run the threads. + return viewer.run(); +} + diff --git a/include/osg/Image b/include/osg/Image index 0c82ff8b7..1facfcaaa 100644 --- a/include/osg/Image +++ b/include/osg/Image @@ -442,12 +442,31 @@ class OSG_EXPORT Image : public BufferData /** Pass frame information to the custom Image classes, to be called only when objects associated with imagery are not culled.*/ virtual void setFrameLastRendered(const osg::FrameStamp* /*frameStamp*/) {} + + class DimensionsChangedCallback : public osg::Referenced { + public: + DimensionsChangedCallback() : osg::Referenced() {} + virtual void operator()(osg::Image* image) = 0; + }; + + typedef std::vector< osg::ref_ptr > DimensionsChangedCallbackVector; + + void addDimensionsChangedCallback(DimensionsChangedCallback* cb); + void removeDimensionsChangedCallback(DimensionsChangedCallback* cb); protected : virtual ~Image(); Image& operator = (const Image&) { return *this; } + + void handleDimensionsChangedCallbacks() + { + for(DimensionsChangedCallbackVector::iterator i = _dimensionsChangedCallbacks.begin(); i != _dimensionsChangedCallbacks.end(); ++i) + { + (*i)->operator()(this); + } + } std::string _fileName; WriteHint _writeHint; @@ -473,6 +492,8 @@ class OSG_EXPORT Image : public BufferData MipmapDataType _mipmapData; ref_ptr _bufferObject; + + DimensionsChangedCallbackVector _dimensionsChangedCallbacks; }; class Geode; diff --git a/include/osg/ImageStream b/include/osg/ImageStream index a18d59f07..4257ab7d3 100644 --- a/include/osg/ImageStream +++ b/include/osg/ImageStream @@ -57,7 +57,7 @@ class OSG_EXPORT ImageStream : public Image virtual void quit(bool /*waitForThreadToExit*/ = true) {} - StreamStatus getStatus() { return _status; } + StreamStatus getStatus() const { return _status; } enum LoopingMode @@ -89,6 +89,10 @@ class OSG_EXPORT ImageStream : public Image virtual void setVolume(float) {} virtual float getVolume() const { return 0.0f; } + + /// set the balance of the audio: -1 = left, 0 = center, 1 = right + virtual float getAudioBalance() { return 0.0f; } + virtual void setAudioBalance(float b) {} typedef std::vector< osg::ref_ptr > AudioStreams; void setAudioStreams(const AudioStreams& asl) { _audioStreams = asl; } diff --git a/src/osg/Image.cpp b/src/osg/Image.cpp index b74fbdb55..bc6846029 100644 --- a/src/osg/Image.cpp +++ b/src/osg/Image.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -213,7 +214,8 @@ Image::Image() _packing(4), _pixelAspectRatio(1.0), _allocationMode(USE_NEW_DELETE), - _data(0L) + _data(0L), + _dimensionsChangedCallbacks() { setDataVariance(STATIC); } @@ -232,7 +234,8 @@ Image::Image(const Image& image,const CopyOp& copyop): _pixelAspectRatio(image._pixelAspectRatio), _allocationMode(USE_NEW_DELETE), _data(0L), - _mipmapData(image._mipmapData) + _mipmapData(image._mipmapData), + _dimensionsChangedCallbacks(image._dimensionsChangedCallbacks) { if (image._data) { @@ -853,6 +856,8 @@ void Image::allocateImage(int s,int t,int r, int packing) { _mipmapData.clear(); + + bool callback_needed(false); unsigned int previousTotalSize = 0; @@ -870,6 +875,7 @@ void Image::allocateImage(int s,int t,int r, if (_data) { + callback_needed = (_s != s) || (_t != t) || (_r != r); _s = s; _t = t; _r = r; @@ -884,7 +890,8 @@ void Image::allocateImage(int s,int t,int r, } else { - + callback_needed = (_s != 0) || (_t != 0) || (_r != 0); + // failed to allocate memory, for now, will simply set values to 0. _s = 0; _t = 0; @@ -898,7 +905,10 @@ void Image::allocateImage(int s,int t,int r, // policy so that allocateImage honours previous settings of _internalTextureFormat. //_internalTextureFormat = 0; } - + + if (callback_needed) + handleDimensionsChangedCallbacks(); + dirty(); } @@ -911,7 +921,9 @@ void Image::setImage(int s,int t,int r, int rowLength) { _mipmapData.clear(); - + + bool callback_needed = (_s != s) || (_t != t) || (_r != r); + _s = s; _t = t; _r = r; @@ -926,6 +938,9 @@ void Image::setImage(int s,int t,int r, _rowLength = rowLength; dirty(); + + if (callback_needed) + handleDimensionsChangedCallbacks(); } @@ -1758,3 +1773,14 @@ Vec4 Image::getColor(const Vec3& texcoord) const //OSG_NOTICE<<"getColor("< +#include +#include "OSXQTKitVideo.h" + + +class OSXCoreVideoAdapter : public osg::Referenced { + + public: + OSXCoreVideoAdapter(osg::State& state, osg::Image* image); + + void setVideo(osg::Image* image); + + void setTimeStamp(const CVTimeStamp* ts) {_timestamp = ts; getFrame();} + bool getFrame(); + + inline GLenum getTextureName() { return _currentTexName; } + inline GLenum getTextureTarget() { return _currentTexTarget; } + + QTVisualContextRef getVisualContext() { return _context; } + + virtual ~OSXCoreVideoAdapter(); + + private: + osg::ref_ptr _video; + QTVisualContextRef _context; + const CVTimeStamp* _timestamp; + CVOpenGLTextureRef _currentFrame; + GLint _currentTexName; + GLenum _currentTexTarget; + +}; + + + diff --git a/src/osgPlugins/QTKit/OSXCoreVideoAdapter.mm b/src/osgPlugins/QTKit/OSXCoreVideoAdapter.mm new file mode 100644 index 000000000..a8cd39c64 --- /dev/null +++ b/src/osgPlugins/QTKit/OSXCoreVideoAdapter.mm @@ -0,0 +1,127 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#include "OSXCoreVideoAdapter.h" +#include +#include +#include +#import + + + + +OSXCoreVideoAdapter::OSXCoreVideoAdapter(osg::State& state, osg::Image* image) : + osg::Referenced(), + _context(NULL), + _timestamp(NULL), + _currentFrame(NULL), + _currentTexTarget(GL_TEXTURE_RECTANGLE_EXT) +{ + setVideo(image); + if (!_video.valid()) + return; + + + CGLContextObj cglcntx(NULL); + CGLPixelFormatObj cglPixelFormat; + OSStatus err = noErr; + + if (cglcntx == NULL) { + osgViewer::GraphicsWindowCocoa* win = dynamic_cast(state.getGraphicsContext()); + if (win) + { + NSOpenGLContext* context = win->getContext(); + cglcntx = (CGLContextObj)[context CGLContextObj]; + cglPixelFormat = (CGLPixelFormatObj)[ win->getPixelFormat() CGLPixelFormatObj]; + } + } + + + if ((cglcntx == NULL) || (err != noErr)) { + OSG_WARN <<"CoreVideoTexture: could not get Context/Pixelformat " << err << std::endl; + return; + } + + CFTypeRef keys[] = { kQTVisualContextWorkingColorSpaceKey }; + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CFDictionaryRef textureContextAttributes = CFDictionaryCreate(kCFAllocatorDefault, + (const void **)keys, + (const void **)&colorSpace, 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + err = QTOpenGLTextureContextCreate(kCFAllocatorDefault, cglcntx, cglPixelFormat, textureContextAttributes, &_context); + + setVideo(_video.get()); + setTimeStamp(NULL); +} + + + +OSXCoreVideoAdapter::~OSXCoreVideoAdapter() +{ + setVideo(NULL); + + if (_currentFrame) { + CVOpenGLTextureRelease(_currentFrame); + _currentFrame = NULL; + } + + // release the OpenGL Texture Context + if (_context) { + CFRelease(_context); + _context = NULL; + } + +} + + + void OSXCoreVideoAdapter::setVideo(osg::Image* image) + { + if (_video.valid()) { + _video->setCoreVideoAdapter(NULL); + } + _video = dynamic_cast(image); + + if ((_context) && (_video.valid())) + { + _video->setCoreVideoAdapter(this); + setTimeStamp(NULL); + } +} + + + +bool OSXCoreVideoAdapter::getFrame() +{ + QTVisualContextTask(_context); + bool b = QTVisualContextIsNewImageAvailable(_context, _timestamp); + if (b){ + + CVOpenGLTextureRef newFrame; + QTVisualContextCopyImageForTime(_context, kCFAllocatorDefault, _timestamp, &newFrame); + + if (_currentFrame) + CVOpenGLTextureRelease(_currentFrame); + + _currentFrame = newFrame; + + _currentTexTarget = CVOpenGLTextureGetTarget(_currentFrame); + _currentTexName = CVOpenGLTextureGetName(_currentFrame); + } + //std::cerr << _movie->getFileName() << ": " << b << " / " << _movie->isPlaying() << " " << _movie->getCurrentTime() << std::endl; + return b; +} + + diff --git a/src/osgPlugins/QTKit/OSXCoreVideoTexture.cpp b/src/osgPlugins/QTKit/OSXCoreVideoTexture.cpp new file mode 100644 index 000000000..a97485f31 --- /dev/null +++ b/src/osgPlugins/QTKit/OSXCoreVideoTexture.cpp @@ -0,0 +1,147 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + + +#include "OSXCoreVideoTexture.h" + + + +OSXCoreVideoTexture::OSXCoreVideoTexture() + : osg::Texture() + , _textureTarget(GL_TEXTURE_RECTANGLE_EXT) + , _inited(false) + , _adapter(NULL) +{ +} + + +OSXCoreVideoTexture::OSXCoreVideoTexture(osg::Image* image): + osg::Texture(), + _textureTarget(GL_TEXTURE_RECTANGLE_EXT), + _inited(false), + _adapter(NULL) +{ + setImage(image); +} + + +OSXCoreVideoTexture::OSXCoreVideoTexture(const OSXCoreVideoTexture& text,const osg::CopyOp& copyop) : + osg::Texture(text, copyop), + _textureTarget(text._textureTarget), + _inited(text._inited), + _adapter(text._adapter), + _image(text._image) +{ +} + + +OSXCoreVideoTexture::~OSXCoreVideoTexture() { +} + + +int OSXCoreVideoTexture::compare(const osg::StateAttribute& sa) const { + COMPARE_StateAttribute_Types(OSXCoreVideoTexture,sa) + + if (_image!=rhs._image) // smart pointer comparison. + { + if (_image.valid()) + { + if (rhs._image.valid()) + { + int result = _image->compare(*rhs._image); + if (result!=0) return result; + } + else + { + return 1; // valid lhs._image is greater than null. + } + } + else if (rhs._image.valid()) + { + return -1; // valid rhs._image is greater than null. + } + } + + if (!_image && !rhs._image) + { + // no image attached to either Texture2D + // but could these textures already be downloaded? + // check the _textureObjectBuffer to see if they have been + // downloaded + + int result = compareTextureObjects(rhs); + if (result!=0) return result; + } + + int result = compareTexture(rhs); + if (result!=0) return result; + + // compare each paramter in turn against the rhs. +#if 1 + if (_textureWidth != 0 && rhs._textureWidth != 0) + { + COMPARE_StateAttribute_Parameter(_textureWidth) + } + if (_textureHeight != 0 && rhs._textureHeight != 0) + { + COMPARE_StateAttribute_Parameter(_textureHeight) + } +#endif + return 0; // passed all the above comparison macro's, must be equal. +} + + + +void OSXCoreVideoTexture::setImage(osg::Image* image) +{ + if (_image == image) return; + + if (_image.valid() && _image->requiresUpdateCall()) + { + setUpdateCallback(0); + setDataVariance(osg::Object::STATIC); + } + + _image = image; + _modifiedCount.setAllElementsTo(0); + + if (_image.valid() && _image->requiresUpdateCall()) + { + setUpdateCallback(new osg::Image::UpdateCallback()); + setDataVariance(osg::Object::DYNAMIC); + } + _adapter = NULL; +} + + + + + +void OSXCoreVideoTexture::apply(osg::State& state) const { + if (!_image.valid()) + return; + + if (!_adapter.valid()) { + OSXQTKitVideo* m = dynamic_cast(_image.get()); + if ((m) && (m->getCoreVideoAdapter())) + _adapter = m->getCoreVideoAdapter(); + else + _adapter = new OSXCoreVideoAdapter(state, _image.get()); + } + _adapter->getFrame(); + _textureTarget = _adapter->getTextureTarget(); + + glBindTexture(_textureTarget, _adapter->getTextureName()); +} + + diff --git a/src/osgPlugins/QTKit/OSXCoreVideoTexture.h b/src/osgPlugins/QTKit/OSXCoreVideoTexture.h new file mode 100644 index 000000000..60095dc5a --- /dev/null +++ b/src/osgPlugins/QTKit/OSXCoreVideoTexture.h @@ -0,0 +1,80 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#pragma once + + +#include +#include "OSXCoreVideoAdapter.h" + + + +class OSXCoreVideoTexture : public osg::Texture { + + public: + + OSXCoreVideoTexture(); + + OSXCoreVideoTexture(osg::Image* image); + + OSXCoreVideoTexture(const OSXCoreVideoTexture& text,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + META_StateAttribute( , OSXCoreVideoTexture, TEXTURE); + + virtual int compare(const osg::StateAttribute& rhs) const; + + virtual GLenum getTextureTarget() const { return _textureTarget; } + + + virtual void setImage(unsigned int, osg::Image* image) { setImage(image); } + + void setImage(osg::Image* image); + + osg::Image* getImage() { return _image.get(); } + const osg::Image* getImage() const { return _image.get(); } + + virtual osg::Image* getImage(unsigned int) { return _image.get(); } + + virtual const osg::Image* getImage(unsigned int) const { return _image.get(); } + + virtual unsigned int getNumImages() const { return 1; } + + virtual int getTextureWidth() const { return _textureWidth; } + virtual int getTextureHeight() const { return _textureHeight; } + virtual int getTextureDepth() const { return 1; } + + + virtual void apply(osg::State& state) const; + + virtual void allocateMipmap(osg::State& state) const {} + + inline unsigned int& getModifiedCount(unsigned int contextID) const + { + return _modifiedCount[contextID]; + } + + protected: + virtual void computeInternalFormat() const {} + virtual ~OSXCoreVideoTexture(); + + mutable GLenum _textureTarget; + int _textureWidth; + int _textureHeight; + bool _inited; + mutable osg::ref_ptr _adapter; + osg::ref_ptr _image; + + typedef osg::buffered_value ImageModifiedCount; + mutable ImageModifiedCount _modifiedCount; + +}; diff --git a/src/osgPlugins/QTKit/OSXQTKitVideo.h b/src/osgPlugins/QTKit/OSXQTKitVideo.h new file mode 100644 index 000000000..2cf6ec728 --- /dev/null +++ b/src/osgPlugins/QTKit/OSXQTKitVideo.h @@ -0,0 +1,99 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#pragma once + +#include +#include "VideoFrameDispatcher.h" + +#ifdef __OBJC__ +@class QTMovie; +#else +class QTMovie; +#endif + +class QTVisualContext; +class OSXCoreVideoAdapter; + +class OSXQTKitVideo : public osgVideo::VideoImageStream { + +public: + OSXQTKitVideo(); + ~OSXQTKitVideo(); + + virtual void setTimeMultiplier(double r); + virtual double getTimeMultiplier() const; + + virtual double getCurrentTime() const; + + virtual bool isPlaying() const { return (getStatus() == PLAYING); } + + virtual bool valid() const { return (getStatus() != INVALID); } + + void open(const std::string& file_name); + + virtual void setVolume (float); + virtual float getVolume () const; + + virtual float getAudioBalance(); + virtual void setAudioBalance(float b); + + virtual double getLength() const { return _duration; } + + virtual void seek (double t); + virtual void play (); + virtual void pause (); + + void setCoreVideoAdapter(OSXCoreVideoAdapter* adapter); + OSXCoreVideoAdapter* getCoreVideoAdapter() const { return _coreVideoAdapter; } + + void decodeFrame(bool force); + + virtual void decodeFrame() { decodeFrame(_waitForFirstFrame); } + + virtual bool requiresUpdateCall () const { return (!getCoreVideoAdapter() && !getVideoFrameDispatcher() ); } + + virtual void update(osg::NodeVisitor *) + { + requestNewFrame(_waitForFirstFrame); + } + + void requestNewFrame(bool force) + { + if (!setNeedsDispatching(RequestSingleUpdate)) + decodeFrame(force); + else + _waitForFirstFrame = true; + } + + virtual bool needsDispatching() const + { + return _waitForFirstFrame || getNeedsDispatching(); + } + +protected: + + virtual void applyLoopingMode(); + struct Data; +private: + bool _isActive, _isValid; + double _duration; + QTMovie* _movie; + Data* _data; + mutable double _rate; + bool _waitForFirstFrame; + OSXCoreVideoAdapter* _coreVideoAdapter; + + +}; + diff --git a/src/osgPlugins/QTKit/OSXQTKitVideo.mm b/src/osgPlugins/QTKit/OSXQTKitVideo.mm new file mode 100644 index 000000000..6be2d9539 --- /dev/null +++ b/src/osgPlugins/QTKit/OSXQTKitVideo.mm @@ -0,0 +1,370 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#include + +#include +#include +#include + +#include "OSXQTKitVideo.h" +#include "OSXCoreVideoAdapter.h" + +namespace { + +static NSString* toNSString(const std::string& str) +{ + return [NSString stringWithUTF8String: str.c_str()]; +} + +static std::string toString(NSString* str) +{ + return str ? std::string([str UTF8String]) : ""; +} + + +class NSAutoreleasePoolHelper { +public: + NSAutoreleasePoolHelper() + { + _pool = [[NSAutoreleasePool alloc] init]; + } + + ~NSAutoreleasePoolHelper() + { + [_pool release]; + } + +private: + NSAutoreleasePool* _pool; +}; + +} + +@interface NotificationHandler : NSObject { + OSXQTKitVideo* video; +} + +@property (readwrite,assign) OSXQTKitVideo* video; + +- (void) movieNaturalSizeDidChange:(NSNotification*)the_notification; +- (void) movieLoadStateDidChange:(NSNotification*)the_notification; + +@end + +@implementation NotificationHandler + +@synthesize video; + +- (void) movieNaturalSizeDidChange:(NSNotification*)the_notification +{ + video->requestNewFrame(true); +} + +- (void) movieLoadStateDidChange:(NSNotification*)the_notification +{ + video->requestNewFrame(true); +} + + +@end + + +struct OSXQTKitVideo::Data { + QTVisualContextRef visualContext; + CVPixelBufferRef lastFrame; + NotificationHandler* notificationHandler; + Data() : visualContext(NULL), lastFrame(NULL) {} +}; + + + +OSXQTKitVideo::OSXQTKitVideo() + : osgVideo::VideoImageStream() + , _rate(0.0) + , _coreVideoAdapter(NULL) +{ + _status = INVALID; + _data = new Data(); + _data->notificationHandler = [[NotificationHandler alloc] init]; + _data->notificationHandler.video = this; + + setOrigin(osg::Image::TOP_LEFT); +} + + +OSXQTKitVideo::~OSXQTKitVideo() +{ + _status = INVALID; + + NSAutoreleasePoolHelper autorelease_pool_helper; + + [[NSNotificationCenter defaultCenter] removeObserver:_data->notificationHandler + name:QTMovieLoadStateDidChangeNotification object:_movie]; + + [[NSNotificationCenter defaultCenter] removeObserver:_data->notificationHandler +#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) + name:QTMovieNaturalSizeDidChangeNotification +#else + name:QTMovieSizeDidChangeNotification +#endif + object:_movie]; + + [_movie stop]; + [_movie invalidate]; + [_movie release]; + + if (_data->visualContext) + QTVisualContextRelease(_data->visualContext); + + if (_data->lastFrame) + { + CFRelease(_data->lastFrame); + CVPixelBufferRelease(_data->lastFrame); + } + + [_data->notificationHandler release]; + + delete _data; +} + + + +void OSXQTKitVideo::open(const std::string& file_name) +{ + bool valid = true; + NSAutoreleasePoolHelper autorelease_pool_helper; + + NSError* error; + + NSMutableDictionary* movieAttributes = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:NO], QTMovieOpenAsyncOKAttribute, + nil]; + + if (osgDB::containsServerAddress(file_name)) + [movieAttributes setObject:[NSURL URLWithString: toNSString(file_name)] forKey: QTMovieURLAttribute]; + else + [movieAttributes setObject:[NSURL fileURLWithPath: toNSString(file_name)] forKey: QTMovieURLAttribute]; + + + _movie = [[QTMovie alloc] initWithAttributes:movieAttributes + error: &error]; + + if(error || _movie == NULL) + { + OSG_WARN << "OSXQTKitVideo: could not load movie from " << file_name << std::endl; + valid = false; + } + + NSSize movie_size = [[_movie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]; + + QTGetTimeInterval([_movie duration], &_duration); + + + NSDictionary *pixelBufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys: + //in general this shouldn't be forced. but in order to ensure we get good pixels use this one + [NSNumber numberWithInt: kCVPixelFormatType_32BGRA], (NSString*)kCVPixelBufferPixelFormatTypeKey, + [NSNumber numberWithInteger:1], kCVPixelBufferBytesPerRowAlignmentKey, + [NSNumber numberWithBool:YES], kCVPixelBufferOpenGLCompatibilityKey, + //specifying width and height can't hurt since we know + nil]; + + NSMutableDictionary *ctxAttributes = [NSMutableDictionary dictionaryWithObject:pixelBufferAttributes + forKey:(NSString*)kQTVisualContextPixelBufferAttributesKey]; + + OSStatus err = QTPixelBufferContextCreate(kCFAllocatorDefault, (CFDictionaryRef)ctxAttributes, &_data->visualContext); + if(err) + { + OSG_WARN << "OSXQTKitVideo: could not create Pixel Buffer: " << err << std::endl; + valid = false; + } + + allocateImage((int)movie_size.width,(int)movie_size.height,1,GL_BGRA,GL_UNSIGNED_INT_8_8_8_8_REV,1); + + setInternalTextureFormat(GL_RGBA8); + + SetMovieVisualContext([_movie quickTimeMovie], _data->visualContext); + + [[NSNotificationCenter defaultCenter] addObserver:_data->notificationHandler + selector:@selector(movieNaturalSizeDidChange:) +#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) + name:QTMovieNaturalSizeDidChangeNotification +#else + name:QTMovieSizeDidChangeNotification +#endif + object:_movie]; + + [[NSNotificationCenter defaultCenter] addObserver:_data->notificationHandler + selector:@selector(movieLoadStateDidChange:) + name:QTMovieLoadStateDidChangeNotification + object:_movie]; + + applyLoopingMode(); + + _waitForFirstFrame = true; + requestNewFrame(true); + + _status = (valid) ? PAUSED : INVALID; +} + + + +void OSXQTKitVideo::setTimeMultiplier(double r) +{ + if (!valid()) + return; + + NSAutoreleasePoolHelper pool; + _rate = r; + [_movie setRate: _rate]; + + _status = (_rate != 0) ? PLAYING : PAUSED; + setNeedsDispatching( _status == PLAYING ? RequestContinuousUpdate : StopUpdate ); +} + + +double OSXQTKitVideo::getTimeMultiplier() const +{ + NSAutoreleasePoolHelper pool; + _rate = [_movie rate]; + return _rate; +} + + +void OSXQTKitVideo::setVolume (float f) +{ + NSAutoreleasePoolHelper pool; + + [_movie setVolume: f]; +} + + + +float OSXQTKitVideo::getVolume () const +{ + NSAutoreleasePoolHelper pool; + + return [_movie volume]; +} + + + +float OSXQTKitVideo::getAudioBalance() +{ + float balance; + GetMovieAudioBalance([_movie quickTimeMovie], &balance, 0); + return balance; +} + + +void OSXQTKitVideo::setAudioBalance(float b) +{ + SetMovieAudioBalance([_movie quickTimeMovie], b, 0); +} + + +void OSXQTKitVideo::seek (double t) +{ + NSAutoreleasePoolHelper pool; + [_movie setCurrentTime: QTMakeTimeWithTimeInterval(t)]; + if (!isPlaying()) + requestNewFrame(true); +} + + +void OSXQTKitVideo::play () +{ + setTimeMultiplier(1.0); +} + + +void OSXQTKitVideo::pause () +{ + setTimeMultiplier(0.0); +} + +void OSXQTKitVideo::applyLoopingMode() +{ + NSAutoreleasePoolHelper pool; + [_movie setAttribute:[NSNumber numberWithBool:(getLoopingMode() == LOOPING) ] forKey:QTMovieLoopsAttribute]; +} + + +double OSXQTKitVideo::getCurrentTime() const +{ + double t; + QTGetTimeInterval([_movie currentTime], &t); + return t; +} + + + +void OSXQTKitVideo::setCoreVideoAdapter(OSXCoreVideoAdapter* adapter) +{ + _coreVideoAdapter = adapter; + SetMovieVisualContext([_movie quickTimeMovie], _coreVideoAdapter ? _coreVideoAdapter->getVisualContext() : _data->visualContext ); +} + + + + +void OSXQTKitVideo::decodeFrame(bool force) +{ + if(getCoreVideoAdapter()) + return; + + + QTVisualContextTask(_data->visualContext); + + CVPixelBufferRef currentFrame(NULL); + const CVTimeStamp* in_output_time(NULL); + + if(!force && !QTVisualContextIsNewImageAvailable(_data->visualContext, in_output_time)) + return; + + OSStatus error_status = QTVisualContextCopyImageForTime(_data->visualContext, kCFAllocatorDefault, in_output_time, ¤tFrame); + + if ((noErr == error_status) && (NULL != currentFrame)) + { + if (_waitForFirstFrame) { + _waitForFirstFrame = false; + } + + if (_data->lastFrame) { + CFRelease(_data->lastFrame); + CVPixelBufferRelease(_data->lastFrame); + } + + size_t buffer_width = CVPixelBufferGetWidth(currentFrame); + size_t buffer_height = CVPixelBufferGetHeight(currentFrame); + + CVPixelBufferLockBaseAddress( currentFrame, kCVPixelBufferLock_ReadOnly ); + + void* raw_pixel_data = CVPixelBufferGetBaseAddress(currentFrame); + + + setImage(buffer_width,buffer_height,1, + GL_RGBA8, + GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8_REV, + (unsigned char *)raw_pixel_data, + osg::Image::NO_DELETE,1); + + CVPixelBufferUnlockBaseAddress( currentFrame, 0 ); + + _data->lastFrame = currentFrame; + CFRetain(_data->lastFrame); + dirty(); + } +} + diff --git a/src/osgPlugins/QTKit/ReaderWriterQTKit.cpp b/src/osgPlugins/QTKit/ReaderWriterQTKit.cpp new file mode 100644 index 000000000..6e367a4dd --- /dev/null +++ b/src/osgPlugins/QTKit/ReaderWriterQTKit.cpp @@ -0,0 +1,150 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + + +/* README: + * + * This code is loosely based on the QTKit implementation of Eric Wing, I removed + * some parts and added other parts. + * + * What's new: + * - it can handle URLs currently http and rtsp + * - it supports OS X's CoreVideo-technology, this will render the movie-frames + * into a bunch of textures. If you load your movie via readImageFile you'll + * get the standard behaviour, an ImageStream, where the data gets updated on + * every new video-frame. This may be slow. + * To get CoreVideo, you'll need to use readObjectFile and cast the result (if any) + * to an osg::Texture and use that as your video-texture. If you need access to the + * imagestream, just cast getImage to an image-stream. Please note, the data- + * property of the image-stream does NOT store the current frame, instead it's empty. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include "OSXQTKitVideo.h" +#include "OSXCoreVideoTexture.h" +#include "VideoFrameDispatcher.h" + + + + +class ReaderWriterQTKit : public osgDB::ReaderWriter +{ + public: + + ReaderWriterQTKit() + { + supportsExtension("mov","Quicktime movie format"); + supportsExtension("mpg","Mpeg movie format"); + supportsExtension("mp4","Mpeg movie format"); + supportsExtension("m4v","Mpeg movie format"); + supportsExtension("flv","Flash video file (if Perian is installed)"); + supportsExtension("dv","dv movie format"); + supportsExtension("avi","avi movie format (if Perian/WMV is installed)"); + supportsExtension("sdp","sdp movie format"); + supportsExtension("swf","swf movie format (if Perian is installed)"); + supportsExtension("3gp","3gp movie format"); + + supportsProtocol("http", "streaming media per http"); + supportsProtocol("rtsp", "streaming media per rtsp"); + + supportsOption("disableCoreVideo", "disable the usage of coreVideo when using readObjectFile, returns an ImageStream instead"); + supportsOption("disableMultiThreadedFrameDispatching", "disable the usage of the multithreade VideoFrameDispatcher to decode video frames"); + + } + + + virtual ~ReaderWriterQTKit() + { + OSG_INFO<<"~ReaderWriterQTKit()"< video = new OSXQTKitVideo(); + bool disable_multi_threaded_frame_dispatching = options ? (options->getPluginStringData("disableMultiThreadedFrameDispatching") == "true"): false; + bool disable_core_video = options ? (options->getPluginStringData("disableCoreVideo") == "true") : false; + OSG_INFO << "disableMultiThreadedFrameDispatching: " << disable_multi_threaded_frame_dispatching << std::endl; + OSG_INFO << "disableCoreVideo : " << disable_core_video << std::endl; + + if (!options + || (!disable_multi_threaded_frame_dispatching + && disable_core_video)) + { + static osg::ref_ptr video_frame_dispatcher(NULL); + if (!video_frame_dispatcher) { + std::string num_threads_str = options ? options->getPluginStringData("numFrameDispatchThreads") : "0"; + video_frame_dispatcher = new osgVideo::VideoFrameDispatcher(atoi(num_threads_str.c_str())); + } + video_frame_dispatcher->addVideo(video); + } + + video->open(fileName); + + return video->valid() ? video.release() : NULL; + } + + virtual ReadResult readObject (const std::string &file, const osgDB::ReaderWriter::Options* options) const + { + ReadResult rr = readImage(file, options); + if (!rr.validImage()) + return rr; + bool use_core_video = true; + + if (options && !options->getPluginStringData("disableCoreVideo").empty()) + use_core_video = false; + + osg::ref_ptr video = dynamic_cast(rr.getImage()); + if (!video || !use_core_video) + return rr; + + osg::ref_ptr texture = new OSXCoreVideoTexture(video); + return texture.release(); + } + + protected: + + +}; + + + +// now register with Registry to instantiate the above +// reader/writer. +REGISTER_OSGPLUGIN(QTKit, ReaderWriterQTKit) diff --git a/src/osgPlugins/QTKit/VideoFrameDispatcher.cpp b/src/osgPlugins/QTKit/VideoFrameDispatcher.cpp new file mode 100644 index 000000000..d4cfae16d --- /dev/null +++ b/src/osgPlugins/QTKit/VideoFrameDispatcher.cpp @@ -0,0 +1,187 @@ + +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#include "VideoFrameDispatcher.h" +#include +#include + +namespace osgVideo { + +VideoImageStream::VideoImageStream() + : osg::ImageStream() + , _needsDispatching(false) + , _dispatcher(NULL) + , _queue(NULL) +{ +} + +VideoImageStream::VideoImageStream(const VideoImageStream& image,const osg::CopyOp& copyop) + : osg::ImageStream(image, copyop) + , _needsDispatching(image._needsDispatching) + , _dispatcher(image._dispatcher) + , _queue(NULL) +{ +} + +VideoImageStream::~VideoImageStream() +{ + setNeedsDispatching(StopUpdate); + _dispatcher = NULL; +} + +bool VideoImageStream::setNeedsDispatching(RequestMode request_mode) +{ + _needsDispatching = (_needsDispatching || (request_mode == RequestContinuousUpdate)) && (request_mode != StopUpdate); + if (!_dispatcher) + return false; + + if (request_mode == StopUpdate) { + _dispatcher->removeFromQueue(this); + } + else + { + _dispatcher->addToQueue(this); + } + + return (_dispatcher != NULL); +} + +#pragma mark + +VideoFrameDispatchQueue::VideoFrameDispatchQueue() + : OpenThreads::Thread() + , osg::Referenced() + , _queue() + , _numItems(0) + , _block() + , _mutex() + , _finished(false) +{ +} + +void VideoFrameDispatchQueue::run() +{ + osg::Timer t; + static unsigned int frame_delay = 1000 * 1000 / 120; + + _block.reset(); + _block.block(); + + while(!_finished) + { + unsigned int num_items(0); + { + osg::Timer_t last_tick(t.tick()); + OpenThreads::ScopedLock lock(_mutex); + for(Queue::iterator i = _queue.begin(); i != _queue.end(); ) + { + osg::observer_ptr stream(*i); + + if (stream.valid() && stream->needsDispatching()) + { + if (stream.valid()) + stream->decodeFrame(); + ++num_items; + ++i; + } + else + { + if (stream.valid()) + stream->setDispatchQueue(NULL); + _queue.erase(i++); + } + + } + _numItems = num_items; + if (_numItems > 0) + { + unsigned int dt = t.delta_u(last_tick, t.tick()); + if (dt < frame_delay) { + OpenThreads::Thread::microSleep(frame_delay - dt); + } + } + } + + if (_numItems == 0) + { + // std::cout << this << " blocking" << std::endl; + _block.reset(); + _block.block(); + } + + } +} + +void VideoFrameDispatchQueue::addItem(osgVideo::VideoImageStream *stream) +{ + if (_finished) return; + + OpenThreads::ScopedLock lock(_mutex); + _queue.insert(stream); + stream->setDispatchQueue(this); + + _numItems = _queue.size(); + _block.release(); + // std::cout << this << " release" << std::endl; +} + +void VideoFrameDispatchQueue::removeItem(osgVideo::VideoImageStream* stream) +{ + stream->setDispatchQueue(NULL); + OpenThreads::ScopedLock lock(_mutex); + _queue.erase(stream); + _numItems = _queue.size(); +} + +VideoFrameDispatchQueue::~VideoFrameDispatchQueue() +{ + _finished = true; + _block.release(); + join(); +} + +#pragma mark + +VideoFrameDispatcher::VideoFrameDispatcher(unsigned int num_threads) + : osg::Referenced() + , _queues() +{ + num_threads = num_threads ? num_threads : OpenThreads::GetNumberOfProcessors(); + OSG_ALWAYS << "VideoFrameDispatcher: creating " << num_threads << " queues." << std::endl; + for(unsigned int i = 0; i < num_threads; ++i) + { + VideoFrameDispatchQueue* q = new VideoFrameDispatchQueue(); + q->start(); + _queues.push_back(q); + } +} + + +void VideoFrameDispatcher::addToQueue(VideoImageStream *stream) +{ + stream->setThreadSafeRefUnref(true); + if (stream->getDispatchQueue()) + return; + + VideoFrameDispatchQueue* queue = *std::min_element(_queues.begin(), _queues.end(), VideoFrameDispatchQueueComparator()); + queue->addItem(stream); +} + +void VideoFrameDispatcher::removeFromQueue(VideoImageStream* stream) +{ + if (stream->getDispatchQueue()) + stream->getDispatchQueue()->removeItem(stream); +} + +} \ No newline at end of file diff --git a/src/osgPlugins/QTKit/VideoFrameDispatcher.h b/src/osgPlugins/QTKit/VideoFrameDispatcher.h new file mode 100644 index 000000000..8ad6fb14b --- /dev/null +++ b/src/osgPlugins/QTKit/VideoFrameDispatcher.h @@ -0,0 +1,108 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace osgVideo { + +class VideoFrameDispatchQueue; +class VideoFrameDispatcher; + + +class VideoImageStream : public osg::ImageStream { +public: + enum RequestMode { RequestContinuousUpdate, RequestSingleUpdate, StopUpdate }; + + VideoImageStream(); + VideoImageStream(const VideoImageStream& image,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + ~VideoImageStream(); + virtual bool needsDispatching() const { return _needsDispatching; } + + virtual void decodeFrame() = 0; + +protected: + + bool setNeedsDispatching(RequestMode request_mode); + VideoFrameDispatcher* getVideoFrameDispatcher() const { return _dispatcher; } + void setVideoFrameDispatcher(VideoFrameDispatcher* dispatcher) { _dispatcher = dispatcher; } + bool getNeedsDispatching() const { return _needsDispatching; } + + void setDispatchQueue(VideoFrameDispatchQueue* queue) { _queue = queue; } + VideoFrameDispatchQueue* getDispatchQueue() const { return _queue; } + +private: + bool _needsDispatching; + VideoFrameDispatcher* _dispatcher; + VideoFrameDispatchQueue* _queue; + +friend class VideoFrameDispatcher; +friend class VideoFrameDispatchQueue; +}; + +class VideoFrameDispatchQueue: public OpenThreads::Thread, public osg::Referenced { +public: + typedef std::set< osg::observer_ptr > Queue; + + VideoFrameDispatchQueue(); + ~VideoFrameDispatchQueue(); + unsigned int getNumItemsInQueue() const { return _numItems; } + + void addItem(VideoImageStream* stream); + void removeItem(osgVideo::VideoImageStream* stream); + + virtual void run(); + +private: + Queue _queue; + unsigned int _numItems; + OpenThreads::Block _block; + OpenThreads::Mutex _mutex; + bool _finished; +}; + +struct VideoFrameDispatchQueueComparator { + bool operator() (const osg::ref_ptr& lhs, const osg::ref_ptr& rhs) const { + return lhs->getNumItemsInQueue() < rhs->getNumItemsInQueue(); + } +}; + + +class VideoFrameDispatcher : public osg::Referenced { + +public: + + typedef std::vector< osg::ref_ptr > DispatchQueues; + + VideoFrameDispatcher(unsigned int num_threads = 0); + + void addVideo(VideoImageStream* stream) + { + stream->setVideoFrameDispatcher(this); + } + + void addToQueue(VideoImageStream* stream); + void removeFromQueue(VideoImageStream* stream); + +private: + DispatchQueues _queues; + +}; + +} diff --git a/src/osgPlugins/avfoundation/CMakeLists.txt b/src/osgPlugins/avfoundation/CMakeLists.txt new file mode 100644 index 000000000..765acd360 --- /dev/null +++ b/src/osgPlugins/avfoundation/CMakeLists.txt @@ -0,0 +1,17 @@ +INCLUDE_DIRECTORIES( ${AV_FOUNDATION_INCLUDE_DIR} ) + +SET(TARGET_SRC + OSXAVFoundationVideo.mm + OSXAVFoundationVideo.h + ../QTKit/VideoFrameDispatcher.h + ../QTKIt/VideoFrameDispatcher.cpp + OSXAVFoundationCoreVideoTexture.h + OSXAVFoundationCoreVideoTexture.cpp + ReaderWriterAVFoundation.cpp +) + +SET(TARGET_LIBRARIES_VARS AV_FOUNDATION_LIBRARY COCOA_LIBRARY COREVIDEO_LIBRARY COREMEDIA_LIBRARY QUARTZCORE_LIBRARY) +SET(TARGET_ADDED_LIBRARIES osgViewer ) + +#### end var setup ### +SETUP_PLUGIN(AVFoundation) diff --git a/src/osgPlugins/avfoundation/OSXAVFoundationVideo.h b/src/osgPlugins/avfoundation/OSXAVFoundationVideo.h new file mode 100644 index 000000000..24ecec585 --- /dev/null +++ b/src/osgPlugins/avfoundation/OSXAVFoundationVideo.h @@ -0,0 +1,112 @@ +// +// OSXAVFoundationVideo.h +// cefix_presentation_ios +// +// Created by Stephan Maximilian Huber on 25.07.12. +// Copyright (c) 2012 stephanmaximilianhuber.com. All rights reserved. +// + +#pragma once + + + + + +#include +#include "../QTKit/VideoFrameDispatcher.h" + + +class OSXAVFoundationVideo : public osgVideo::VideoImageStream { + +public: + OSXAVFoundationVideo(); + + /// Destructor + ~OSXAVFoundationVideo(); + + virtual Object* clone() const { return new OSXAVFoundationVideo(); } + virtual bool isSameKindAs(const Object* obj) const { + return dynamic_cast(obj) != NULL; + } + + virtual const char* className() const { return "OSXAVFoundationVideo"; } + + /// Start or continue stream. + virtual void play(); + + /** @return true, if a movie is playing */ + + bool isPlaying() const { return (getStatus() == PLAYING); } + + /// sets the movierate + void setTimeMultiplier(double rate); + + /// gets the movierate + double getTimeMultiplier() const; + + /// Pause stream at current position. + virtual void pause(); + + + /// stop playing + virtual void quit(bool /*waitForThreadToExit*/ = true); + + /// Get total length in seconds. + virtual double getLength() const { return _videoDuration; } + + /// jumps to a specific position + virtual void seek(double pos); + + + /// returns the current playing position + virtual double getCurrentTime () const; + + + void open(const std::string& filename); + + /** @return the current volume as float */ + virtual float getVolume() const; + + /** sets the volume of this quicktime to v*/ + virtual void setVolume(float v); + + /** @return the current balance-setting (0 = neutral, -1 = left, 1 = right */ + virtual float getAudioBalance(); + /** sets the current balance-setting (0 = neutral, -1 = left, 1 = right */ + virtual void setAudioBalance(float b); + + virtual double getFrameRate () const { return _framerate; } + + virtual void decodeFrame(); + + virtual bool valid() const { return (getStatus() != INVALID); } + + virtual bool requiresUpdateCall () const { return true; } + + virtual void update(osg::NodeVisitor *); + + virtual void applyLoopingMode(); + + void setUseCoreVideo(bool b) { _useCoreVideo = b; } + bool isCoreVideoUsed() const { return _useCoreVideo; } + void lazyInitCoreVideoTextureCache(osg::State& state); + bool getCurrentCoreVideoTexture(GLenum& target, GLint& name, int& width, int& height) const; +protected: + + virtual bool needsDispatching() const; + + void requestNewFrame(); +private: + class Data; + + void clear(); + + float _videoDuration; + double _volume; + bool _fileOpened, _waitForFrame; + + Data* _data; + bool _useCoreVideo, _dimensionsChangedCallbackNeeded; + double _framerate; + +}; diff --git a/src/osgPlugins/avfoundation/OSXAVFoundationVideo.mm b/src/osgPlugins/avfoundation/OSXAVFoundationVideo.mm new file mode 100644 index 000000000..c3d0bbf82 --- /dev/null +++ b/src/osgPlugins/avfoundation/OSXAVFoundationVideo.mm @@ -0,0 +1,514 @@ +#include "OSXAVFoundationVideo.h" + +#include +#include +#include + +#import +#import + + + +namespace { + +static NSString* toNSString(const std::string& str) +{ + return [NSString stringWithUTF8String: str.c_str()]; +} + + + +static std::string toString(NSString* str) +{ + return str ? std::string([str UTF8String]) : ""; +} +class NSAutoreleasePoolHelper { +public: + NSAutoreleasePoolHelper() + { + _pool = [[NSAutoreleasePool alloc] init]; + } + + ~NSAutoreleasePoolHelper() + { + [_pool release]; + } + +private: + NSAutoreleasePool* _pool; +}; +} + +@interface AVPlayer (MOAdditions) +- (NSURL *)currentURL; +- (void)setVolume:(CGFloat)volume; +@end; + +@implementation AVPlayer (MOAdditions) + +- (NSURL *)currentURL { + AVAsset *asset = self.currentItem.asset; + if ([asset isMemberOfClass:[AVURLAsset class]]) + return ((AVURLAsset *)asset).URL; + return nil; +} + +- (void)setVolume:(CGFloat)volume { + NSArray *audioTracks = [self.currentItem.asset tracksWithMediaType:AVMediaTypeAudio]; + NSMutableArray *allAudioParams = [NSMutableArray array]; + for (AVAssetTrack *track in audioTracks) { + AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; + [audioInputParams setVolume:volume atTime:kCMTimeZero]; + [audioInputParams setTrackID:[track trackID]]; + [allAudioParams addObject:audioInputParams]; + } + AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; + [audioMix setInputParameters:allAudioParams]; + [self.currentItem setAudioMix:audioMix]; +} + +@end + +@interface OSXAVFoundationVideoDelegate : NSObject { + OSXAVFoundationVideo* video; +} +@property (readwrite,assign) OSXAVFoundationVideo* video; + +- (void) playerItemDidReachEnd:(NSNotification*)the_notification; + +@end; + +@implementation OSXAVFoundationVideoDelegate + +@synthesize video; + +- (void) playerItemDidReachEnd:(NSNotification*)the_notification +{ + if (video->getLoopingMode() == osg::ImageStream::LOOPING) { + video->seek(0); + } + else { + video->pause(); + } +} + +@end + + + +class OSXAVFoundationVideo::Data { +public: + AVPlayer* avplayer; + AVPlayerItem* avplayeritem; + AVPlayerItemVideoOutput* output; + OSXAVFoundationVideoDelegate* delegate; + std::vector lastFrames; + int readFrameNdx, writeFrameNdx; + CVOpenGLTextureCacheRef coreVideoTextureCache; + + Data() + : avplayer(NULL) + , avplayeritem(NULL) + , output(NULL) + , delegate(NULL) + , lastFrames(3) + , readFrameNdx(0) + , writeFrameNdx(0) + , coreVideoTextureCache(0) + { + } + ~Data() { + [output release]; + [avplayeritem release]; + [avplayer release]; + + [delegate release]; + + for(unsigned int i=0; i< lastFrames.size(); ++i) + { + if (lastFrames[i]) + { + CVBufferRelease(lastFrames[i]); + } + } + + if (coreVideoTextureCache) + { + CVOpenGLTextureCacheRelease(coreVideoTextureCache); + coreVideoTextureCache = NULL; + } + output = NULL; + avplayer = NULL; + avplayeritem = NULL; + delegate = NULL; + } + + void addFrame(CVBufferRef frame) + { + unsigned int new_ndx = writeFrameNdx + 1; + + if (new_ndx >= lastFrames.size()) + new_ndx = 0; + + if (new_ndx == readFrameNdx) { + new_ndx = readFrameNdx+1; + if (new_ndx >= lastFrames.size()) + new_ndx = 0; + } + + if (lastFrames[new_ndx]) + { + CVBufferRelease(lastFrames[new_ndx]); + } + + lastFrames[new_ndx] = frame; + + writeFrameNdx = new_ndx; + //std::cout << "new frame: " << writeFrameNdx << std::endl; + } + + bool hasNewFrame() const { + return readFrameNdx != writeFrameNdx; + } + + CVBufferRef getLastFrame() { + readFrameNdx = writeFrameNdx; + // std::cout << "get frame: " << readFrameNdx << std::endl; + return lastFrames[readFrameNdx]; + } +}; + +OSXAVFoundationVideo::OSXAVFoundationVideo() + : osgVideo::VideoImageStream() + , _volume(1.0) + , _fileOpened(false) + , _useCoreVideo(false) + , _dimensionsChangedCallbackNeeded(false) +{ + _data = new Data(); + _status = INVALID; + setOrigin(TOP_LEFT); +} + + +OSXAVFoundationVideo::~OSXAVFoundationVideo() +{ + quit(); + if (_data) + delete _data; +} + +void OSXAVFoundationVideo::play() +{ + if (_data->avplayer) { + [_data->avplayer play]; + _status = PLAYING; + setNeedsDispatching(RequestContinuousUpdate); + } +} + + +void OSXAVFoundationVideo::setTimeMultiplier(double rate) +{ + if (_data->avplayer) + { + _data->avplayer.rate = rate; + _status = (rate != 0.0) ? PLAYING : PAUSED; + setNeedsDispatching(_status == PLAYING ? RequestContinuousUpdate: StopUpdate); + } +} + +double OSXAVFoundationVideo::getTimeMultiplier() const +{ + return _data->avplayer ? _data->avplayer.rate : 0.0f; +} + + +void OSXAVFoundationVideo::pause() +{ + if (_data->avplayer) { + [_data->avplayer pause]; + _status = PAUSED; + setNeedsDispatching(StopUpdate); + } +} + + +void OSXAVFoundationVideo::clear() +{ + [_data->output release]; + [_data->avplayeritem release]; + [_data->avplayer release]; + + if (_data->delegate) { + [[NSNotificationCenter defaultCenter] removeObserver: _data->delegate + name:AVPlayerItemDidPlayToEndTimeNotification + object:[_data->avplayer currentItem] + ]; + } + + [_data->delegate release]; + + _data->output = NULL; + _data->avplayer = NULL; + _data->avplayeritem = NULL; + _data->delegate = NULL; +} + + +void OSXAVFoundationVideo::quit(bool t) +{ + pause(); +} + + +void OSXAVFoundationVideo::seek(double pos) +{ + static CMTime tolerance = CMTimeMakeWithSeconds(0.01, 600); + if(_data->avplayer) + [_data->avplayer seekToTime: CMTimeMakeWithSeconds(pos, 600) toleranceBefore: tolerance toleranceAfter: tolerance]; + requestNewFrame(); +} + +double OSXAVFoundationVideo::getCurrentTime () const +{ + return _data->avplayer ? CMTimeGetSeconds([_data->avplayer currentTime]) : 0; +} + +void OSXAVFoundationVideo::open(const std::string& filename) +{ + clear(); + + _data->delegate = [[OSXAVFoundationVideoDelegate alloc] init]; + _data->delegate.video = this; + + NSURL* url(NULL); + if (osgDB::containsServerAddress(filename)) + { + url = [NSURL URLWithString: toNSString(filename)]; + } + else + { + url = [NSURL fileURLWithPath: toNSString(filename)]; + } + + _data->output = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes: + [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey, + [NSNumber numberWithInteger:1], kCVPixelBufferBytesPerRowAlignmentKey, + [NSNumber numberWithBool:YES], kCVPixelBufferOpenGLCompatibilityKey, + nil]]; + + if (_data->output) + { + _data->output.suppressesPlayerRendering = YES; + } + + _data->avplayeritem = [[AVPlayerItem alloc] initWithURL: url]; + _data->avplayer = [AVPlayer playerWithPlayerItem: _data->avplayeritem]; + _data->avplayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; + + [[_data->avplayer currentItem] addOutput:_data->output]; + + [[NSNotificationCenter defaultCenter] addObserver: _data->delegate + selector:@selector(playerItemDidReachEnd:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:[_data->avplayer currentItem]]; + + _videoDuration = CMTimeGetSeconds([[_data->avplayer currentItem] duration]); + + // get the max size of the video-tracks + NSArray* tracks = [_data->avplayeritem.asset tracksWithMediaType: AVMediaTypeVideo]; + CGSize size; + for(unsigned int i=0; i < [tracks count]; ++i) + { + AVAssetTrack* track = [tracks objectAtIndex:i]; + size = track.naturalSize; + _framerate = track.nominalFrameRate; + } + + _s = size.width; + _t = size.height; + _r = 1; + + requestNewFrame(); + + _status = PAUSED; + _fileOpened = true; +} + +float OSXAVFoundationVideo::getVolume() const +{ + return _volume; +} + + +void OSXAVFoundationVideo::setVolume(float v) +{ + _volume = v; + if (_data->avplayer) + [_data->avplayer setVolume: v]; +} + + +float OSXAVFoundationVideo::getAudioBalance() +{ + return 0.0f; +} + + +void OSXAVFoundationVideo::setAudioBalance(float b) +{ + OSG_WARN << "OSXAVFoundationVideo: setAudioBalance not supported!" << std::endl; +} + + + + + +void OSXAVFoundationVideo::decodeFrame() +{ + // std::cout << this << " decodeFrame: " << _waitForFrame << std::endl; + + if (!_fileOpened) + return; + + NSAutoreleasePoolHelper helper; + + bool is_valid = (_data && (_data->avplayer.status != AVPlayerStatusFailed)); + if (!is_valid) + { + _waitForFrame = false; + pause(); + OSG_WARN << "OSXAVFoundationVideo: " << toString([_data->avplayer.error localizedFailureReason]) << std::endl; + } + + bool is_playing = is_valid && (getTimeMultiplier() != 0); + + CMTime outputItemTime = [_data->output itemTimeForHostTime:CACurrentMediaTime()]; + + if (_waitForFrame || [_data->output hasNewPixelBufferForItemTime:outputItemTime]) + { + + CVPixelBufferRef newframe = [_data->output copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL]; + if (newframe) + { + if (isCoreVideoUsed()) + { + CVPixelBufferLockBaseAddress(newframe, kCVPixelBufferLock_ReadOnly); + + CVOpenGLTextureRef texture = NULL; + CVReturn err = CVOpenGLTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _data->coreVideoTextureCache, newframe, 0, &texture); + if (err) + { + OSG_WARN << "OSXAVFoundationVideo :: could not create texture from image, err: " << err << std::endl; + } + int w = CVPixelBufferGetWidth(newframe); + int h = CVPixelBufferGetHeight(newframe); + _dimensionsChangedCallbackNeeded = (_s != w) || (_t != h); + _s = w; _t = h; _r = 1; + + _data->addFrame(texture); + + CVPixelBufferUnlockBaseAddress(newframe, kCVPixelBufferLock_ReadOnly); + CVPixelBufferRelease(newframe); + } + else + { + _data->addFrame(newframe); + } + _waitForFrame = false; + + } + } + + _status = is_valid ? is_playing ? PLAYING : PAUSED : INVALID; +} + +void OSXAVFoundationVideo::update(osg::NodeVisitor *) +{ + if (!getVideoFrameDispatcher()) + decodeFrame(); + + if (isCoreVideoUsed()) + { + if (_dimensionsChangedCallbackNeeded) + handleDimensionsChangedCallbacks(); + _dimensionsChangedCallbackNeeded = false; + + return; + } + + + if (_data->hasNewFrame()) + { + CVPixelBufferRef newframe = _data->getLastFrame(); + + CVPixelBufferLockBaseAddress(newframe,kCVPixelBufferLock_ReadOnly); + + size_t width = CVPixelBufferGetWidth(newframe); + size_t height = CVPixelBufferGetHeight(newframe); + + // Get the base address of the pixel buffer + void *baseAddress = CVPixelBufferGetBaseAddress(newframe); + setImage(width, height, 1, GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE, (unsigned char*)baseAddress, NO_DELETE); + // std::cout << this << " new frame: " << width << "x" << height << " " << baseAddress << std::endl; + CVPixelBufferUnlockBaseAddress(newframe, kCVPixelBufferLock_ReadOnly); + } +} + + +bool OSXAVFoundationVideo::needsDispatching() const +{ + // std::cout << this << " needs dispatching: " << (_waitForFrame || getNeedsDispatching()) << std::endl; + return _waitForFrame || getNeedsDispatching(); +} + + + +void OSXAVFoundationVideo::applyLoopingMode() +{ + // looping is handled by the delegate +} + + +void OSXAVFoundationVideo::requestNewFrame() +{ + setNeedsDispatching(RequestSingleUpdate); + _waitForFrame = true; +} + +bool OSXAVFoundationVideo::getCurrentCoreVideoTexture(GLenum& target, GLint& name, int& width, int& height) const +{ + CVOpenGLTextureCacheFlush(_data->coreVideoTextureCache, 0); + CVOpenGLTextureRef texture = _data->getLastFrame(); + if (texture) + { + target = CVOpenGLTextureGetTarget(texture); + name = CVOpenGLTextureGetName(texture); + width = _s; + height = _t; + } + + return (texture != NULL); +} + + +void OSXAVFoundationVideo::lazyInitCoreVideoTextureCache(osg::State& state) +{ + if (_data->coreVideoTextureCache) + return; + + osgViewer::GraphicsWindowCocoa* win = dynamic_cast(state.getGraphicsContext()); + if (win) + { + NSOpenGLContext* context = win->getContext(); + CGLContextObj cglcntx = (CGLContextObj)[context CGLContextObj]; + CGLPixelFormatObj cglPixelFormat = (CGLPixelFormatObj)[ win->getPixelFormat() CGLPixelFormatObj]; + CVReturn cvRet = CVOpenGLTextureCacheCreate(kCFAllocatorDefault, 0, cglcntx, cglPixelFormat, 0, &_data->coreVideoTextureCache); + if (cvRet != kCVReturnSuccess) + { + OSG_WARN << "OSXAVFoundationVideo : could not create texture cache :" << cvRet << std::endl; + } + } +} + diff --git a/src/osgPlugins/avfoundation/OSXAvFoundationCoreVideoTexture.cpp b/src/osgPlugins/avfoundation/OSXAvFoundationCoreVideoTexture.cpp new file mode 100644 index 000000000..4d4a9e1d4 --- /dev/null +++ b/src/osgPlugins/avfoundation/OSXAvFoundationCoreVideoTexture.cpp @@ -0,0 +1,152 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#include "OSXAVFoundationCoreVideoTexture.h" +#include "OSXAVFoundationVideo.H" +#include + + +OSXAVFoundationCoreVideoTexture::OSXAVFoundationCoreVideoTexture() + : osg::Texture() + , _textureTarget(GL_TEXTURE_RECTANGLE_EXT) + , _textureWidth(0) + , _textureHeight(0) + , _inited(false) +{ +} + + +OSXAVFoundationCoreVideoTexture::OSXAVFoundationCoreVideoTexture(osg::Image* image) + : osg::Texture() + , _textureTarget(GL_TEXTURE_RECTANGLE_EXT) + , _textureWidth(0) + , _textureHeight(0) + , _inited(false) +{ + setImage(image); +} + + +OSXAVFoundationCoreVideoTexture::OSXAVFoundationCoreVideoTexture(const OSXAVFoundationCoreVideoTexture& text,const osg::CopyOp& copyop) + : osg::Texture(text, copyop) + , _textureTarget(text._textureTarget) + , _textureWidth(text._textureWidth) + , _textureHeight(text._textureHeight) + , _inited(text._inited) + , _image(text._image) +{ +} + + +OSXAVFoundationCoreVideoTexture::~OSXAVFoundationCoreVideoTexture() { +} + + +int OSXAVFoundationCoreVideoTexture::compare(const osg::StateAttribute& sa) const { + COMPARE_StateAttribute_Types(OSXAVFoundationCoreVideoTexture,sa) + + if (_image!=rhs._image) // smart pointer comparison. + { + if (_image.valid()) + { + if (rhs._image.valid()) + { + int result = _image->compare(*rhs._image); + if (result!=0) return result; + } + else + { + return 1; // valid lhs._image is greater than null. + } + } + else if (rhs._image.valid()) + { + return -1; // valid rhs._image is greater than null. + } + } + + if (!_image && !rhs._image) + { + // no image attached to either Texture2D + // but could these textures already be downloaded? + // check the _textureObjectBuffer to see if they have been + // downloaded + + int result = compareTextureObjects(rhs); + if (result!=0) return result; + } + + int result = compareTexture(rhs); + if (result!=0) return result; + + // compare each paramter in turn against the rhs. +#if 1 + if (_textureWidth != 0 && rhs._textureWidth != 0) + { + COMPARE_StateAttribute_Parameter(_textureWidth) + } + if (_textureHeight != 0 && rhs._textureHeight != 0) + { + COMPARE_StateAttribute_Parameter(_textureHeight) + } +#endif + return 0; // passed all the above comparison macro's, must be equal. +} + + + +void OSXAVFoundationCoreVideoTexture::setImage(osg::Image* image) +{ + if (_image == image) return; + + if (_image.valid() && _image->requiresUpdateCall()) + { + setUpdateCallback(0); + setDataVariance(osg::Object::STATIC); + } + + _image = image; + _modifiedCount.setAllElementsTo(0); + + if (_image.valid() && _image->requiresUpdateCall()) + { + setUpdateCallback(new osg::Image::UpdateCallback()); + setDataVariance(osg::Object::DYNAMIC); + } + OSXAVFoundationVideo* m = dynamic_cast(_image.get()); + if (m) + m->setUseCoreVideo(true); +} + + + + + +void OSXAVFoundationCoreVideoTexture::apply(osg::State& state) const +{ + if (!_image.valid()) + return; + + OSXAVFoundationVideo* m = dynamic_cast(_image.get()); + if ((m) && (m->isCoreVideoUsed())) + { + m->lazyInitCoreVideoTextureCache(state); + GLint texture_name; + if (m->getCurrentCoreVideoTexture(_textureTarget, texture_name, _textureWidth, _textureHeight)) + { + glBindTexture(_textureTarget, texture_name); + } + } +} + + diff --git a/src/osgPlugins/avfoundation/OSXAvFoundationCoreVideoTexture.h b/src/osgPlugins/avfoundation/OSXAvFoundationCoreVideoTexture.h new file mode 100644 index 000000000..e3c46c30a --- /dev/null +++ b/src/osgPlugins/avfoundation/OSXAvFoundationCoreVideoTexture.h @@ -0,0 +1,76 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#pragma once + +#include + + +class OSXAVFoundationCoreVideoTexture : public osg::Texture { + + public: + + OSXAVFoundationCoreVideoTexture(); + + OSXAVFoundationCoreVideoTexture(osg::Image* image); + + OSXAVFoundationCoreVideoTexture(const OSXAVFoundationCoreVideoTexture& text,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + META_StateAttribute( , OSXAVFoundationCoreVideoTexture, TEXTURE); + + virtual int compare(const osg::StateAttribute& rhs) const; + + virtual GLenum getTextureTarget() const { return _textureTarget; } + + + virtual void setImage(unsigned int, osg::Image* image) { setImage(image); } + + void setImage(osg::Image* image); + + osg::Image* getImage() { return _image.get(); } + const osg::Image* getImage() const { return _image.get(); } + + virtual osg::Image* getImage(unsigned int) { return _image.get(); } + + virtual const osg::Image* getImage(unsigned int) const { return _image.get(); } + + virtual unsigned int getNumImages() const { return 1; } + + virtual int getTextureWidth() const { return _textureWidth; } + virtual int getTextureHeight() const { return _textureHeight; } + virtual int getTextureDepth() const { return 1; } + + + virtual void apply(osg::State& state) const; + + virtual void allocateMipmap(osg::State& state) const {} + + inline unsigned int& getModifiedCount(unsigned int contextID) const + { + return _modifiedCount[contextID]; + } + + protected: + virtual void computeInternalFormat() const {} + virtual ~OSXAVFoundationCoreVideoTexture(); + + mutable GLenum _textureTarget; + mutable int _textureWidth; + mutable int _textureHeight; + bool _inited; + osg::ref_ptr _image; + + typedef osg::buffered_value ImageModifiedCount; + mutable ImageModifiedCount _modifiedCount; + +}; \ No newline at end of file diff --git a/src/osgPlugins/avfoundation/ReaderWriterAVFoundation.cpp b/src/osgPlugins/avfoundation/ReaderWriterAVFoundation.cpp new file mode 100644 index 000000000..7dc5a36db --- /dev/null +++ b/src/osgPlugins/avfoundation/ReaderWriterAVFoundation.cpp @@ -0,0 +1,117 @@ +#include +#include +#include + +#include +#include +#include + +#include "OSXAVFoundationVideo.h" +#include "OSXAVFoundationCoreVideoTexture.h" + +class ReaderWriterAVFoundation : public osgDB::ReaderWriter +{ + public: + + ReaderWriterAVFoundation() + { + supportsExtension("mov","Quicktime movie format"); + supportsExtension("mpg","Mpeg movie format"); + supportsExtension("mp4","Mpeg movie format"); + supportsExtension("m4v","Mpeg movie format"); + supportsExtension("mpeg","Mpeg movie format"); + supportsExtension("avfoundation","AVFoundation movie format"); + + supportsProtocol("http", "streaming media per http"); + supportsProtocol("rtsp", "streaming media per rtsp"); + } + virtual bool acceptsExtension(const std::string& extension) const + { + return + osgDB::equalCaseInsensitive(extension,"mov") || + osgDB::equalCaseInsensitive(extension,"mpg") || + osgDB::equalCaseInsensitive(extension,"mp4") || + osgDB::equalCaseInsensitive(extension,"mpv") || + osgDB::equalCaseInsensitive(extension,"mpeg")|| + osgDB::equalCaseInsensitive(extension,"avfoundation"); + } + + virtual ~ReaderWriterAVFoundation() + { + OSG_INFO<<"~ReaderWriterAVFoundation()"< video = new OSXAVFoundationVideo(); + + bool disable_multi_threaded_frame_dispatching = options ? (options->getPluginStringData("disableMultiThreadedFrameDispatching") == "true") : false; + bool disable_core_video = true; // options ? (options->getPluginStringData("disableCoreVideo") == "true") : false; + OSG_INFO << "disableMultiThreadedFrameDispatching: " << disable_multi_threaded_frame_dispatching << std::endl; + OSG_INFO << "disableCoreVideo : " << disable_core_video << std::endl; + + if (!options + || (!disable_multi_threaded_frame_dispatching + && disable_core_video)) + { + static osg::ref_ptr video_frame_dispatcher(NULL); + if (!video_frame_dispatcher) { + std::string num_threads_str = options ? options->getPluginStringData("numFrameDispatchThreads") : "0"; + video_frame_dispatcher = new osgVideo::VideoFrameDispatcher(atoi(num_threads_str.c_str())); + } + + video_frame_dispatcher->addVideo(video); + } + + video->open(fileName); + + return video->valid() ? video.release() : NULL; + } + + virtual ReadResult readObject (const std::string &file, const osgDB::ReaderWriter::Options* options) const + { + ReadResult rr = readImage(file, options); + if (!rr.validImage()) + return rr; + bool use_core_video = true; + + if (options && !options->getPluginStringData("disableCoreVideo").empty()) + use_core_video = false; + + osg::ref_ptr video = dynamic_cast(rr.getImage()); + if (!video || !use_core_video) + return rr; + + osg::ref_ptr texture = new OSXAVFoundationCoreVideoTexture(video); + return texture.release(); + } + + protected: + + +}; + + + +// now register with Registry to instantiate the above +// reader/writer. +REGISTER_OSGPLUGIN(AVFoundation, ReaderWriterAVFoundation)