Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

From Eric Wing, QTKit plugin for reading movies under OSX using QTKit…

… + CoreVideo
  • Loading branch information...
commit 551eaccf691c3e57081198bff67f0028faf28843 1 parent edff01b
Robert OSFIELD authored
View
2  CMakeLists.txt
@@ -512,6 +512,8 @@ IF(NOT APPLE)
ELSE()
FIND_PACKAGE(QuickTime)
+ FIND_PACKAGE(QTKit)
+ FIND_PACKAGE(CoreVideo)
ENDIF()
################################################################################
View
25 CMakeModules/FindCoreVideo.cmake
@@ -0,0 +1,25 @@
+# Locate Apple CoreVideo (next-generation QuickTime)
+# This module defines
+# COREVIDEO_LIBRARY
+# COREVIDEO_FOUND, if false, do not try to link to gdal
+# COREVIDEO_INCLUDE_DIR, where to find the headers
+#
+# $COREVIDEO_DIR is an environment variable that would
+# correspond to the ./configure --prefix=$COREVIDEO_DIR
+#
+# Created by Eric Wing.
+
+# CoreVideo on OS X looks different than CoreVideo for Windows,
+# so I am going to case the two.
+
+IF(APPLE)
+ FIND_PATH(COREVIDEO_INCLUDE_DIR CoreVideo/CoreVideo.h)
+ FIND_LIBRARY(COREVIDEO_LIBRARY CoreVideo)
+ENDIF()
+
+
+SET(COREVIDEO_FOUND "NO")
+IF(COREVIDEO_LIBRARY AND COREVIDEO_INCLUDE_DIR)
+ SET(COREVIDEO_FOUND "YES")
+ENDIF()
+
View
45 CMakeModules/FindQTKit.cmake
@@ -0,0 +1,45 @@
+# Locate Apple QTKit (next-generation QuickTime)
+# This module defines
+# QTKIT_LIBRARY
+# QTKIT_FOUND, if false, do not try to link to gdal
+# QTKIT_INCLUDE_DIR, where to find the headers
+#
+# $QTKIT_DIR is an environment variable that would
+# correspond to the ./configure --prefix=$QTKIT_DIR
+#
+# Created by Eric Wing.
+
+# QTKit on OS X looks different than QTKit for Windows,
+# so I am going to case the two.
+
+IF(APPLE)
+ FIND_PATH(QTKIT_INCLUDE_DIR QTKit/QTKit.h)
+ FIND_LIBRARY(QTKIT_LIBRARY QTKit)
+ENDIF()
+
+
+SET(QTKIT_FOUND "NO")
+IF(QTKIT_LIBRARY AND QTKIT_INCLUDE_DIR)
+ SET(QTKIT_FOUND "YES")
+ENDIF()
+
+IF(APPLE)
+ # Technically QTKit is 64-bit capable, but the QTKit plug-in currently uses
+ # a few 32-bit only APIs to bridge QTKit and Core Video.
+ # As such, the plugin won't compile for 64-bit until Apple fixes this hole
+ # in their API.
+ # For simplicitly, I pretend QTKit is only 32-bit, but if/when Apple fixes
+ # this, we need an OS version check.
+ # Snow Leopard still lacks a 64-bit path for this.
+ #First check to see if we are running with a native 64-bit compiler (10.6 default) and implicit arch
+ IF(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SIZEOF_VOID_P EQUAL 8)
+ SET(QTKIT_FOUND "NO")
+ ELSE()
+ #Otherwise check to see if 64-bit is explicitly called for.
+ LIST(FIND CMAKE_OSX_ARCHITECTURES "x86_64" has64Compile)
+ IF(NOT has64Compile EQUAL -1)
+ SET(QTKIT_FOUND "NO")
+ ENDIF()
+ ENDIF()
+ENDIF()
+
View
46 src/osgDB/Registry.cpp
@@ -268,9 +268,28 @@ Registry::Registry()
addFileExtensionAlias("png", "imageio");
addFileExtensionAlias("psd", "imageio");
addFileExtensionAlias("tga", "imageio");
+
+#endif
+
+#if defined(DARWIN_QTKIT)
+ addFileExtensionAlias("mov", "QTKit");
+ addFileExtensionAlias("mp4", "QTKit");
+ addFileExtensionAlias("mov", "QTKit");
+ addFileExtensionAlias("mpg", "QTKit");
+ addFileExtensionAlias("mpeg", "QTKit");
+ addFileExtensionAlias("mpv", "QTKit");
+ addFileExtensionAlias("m4v", "QTKit");
+ addFileExtensionAlias("3gp", "QTKit");
+ addFileExtensionAlias("live", "QTKit");
+ // Requires Perian
+ addFileExtensionAlias("avi", "QTKit");
+ addFileExtensionAlias("xvid", "QTKit");
+ // Requires Flip4Mac
+ addFileExtensionAlias("wmv", "QTKit");
#endif
#if defined(DARWIN_QUICKTIME)
+
addFileExtensionAlias("jpg", "qt");
addFileExtensionAlias("jpe", "qt");
addFileExtensionAlias("jpeg", "qt");
@@ -280,17 +299,20 @@ Registry::Registry()
addFileExtensionAlias("png", "qt");
addFileExtensionAlias("psd", "qt");
addFileExtensionAlias("tga", "qt");
- addFileExtensionAlias("mov", "qt");
- addFileExtensionAlias("avi", "qt");
- addFileExtensionAlias("mpg", "qt");
addFileExtensionAlias("flv", "qt");
- addFileExtensionAlias("mpv", "qt");
addFileExtensionAlias("dv", "qt");
- addFileExtensionAlias("mp4", "qt");
- addFileExtensionAlias("m4v", "qt");
- addFileExtensionAlias("3gp", "qt");
- // Add QuickTime live support for OSX
- addFileExtensionAlias("live", "qt");
+ #if !defined(DARWIN_QTKIT)
+
+ addFileExtensionAlias("mov", "qt");
+ addFileExtensionAlias("avi", "qt");
+ addFileExtensionAlias("mpg", "qt");
+ addFileExtensionAlias("mpv", "qt");
+ addFileExtensionAlias("mp4", "qt");
+ addFileExtensionAlias("m4v", "qt");
+ addFileExtensionAlias("3gp", "qt");
+ // Add QuickTime live support for OSX
+ addFileExtensionAlias("live", "qt");
+ #endif
#else
addFileExtensionAlias("jpg", "jpeg");
addFileExtensionAlias("jpe", "jpeg");
@@ -298,6 +320,7 @@ Registry::Registry()
// really need to decide this at runtime...
#if defined(USE_XINE)
+
addFileExtensionAlias("mov", "xine");
addFileExtensionAlias("mpg", "xine");
addFileExtensionAlias("ogv", "xine");
@@ -309,7 +332,10 @@ Registry::Registry()
#endif
// support QuickTime for Windows
- #if defined(USE_QUICKTIME)
+ // Logic error here. It is possible for Apple to not define Quicktime and end up in
+ // this Quicktime for Windows block. So add an extra check to avoid QTKit clashes.
+ #if defined(USE_QUICKTIME) && !defined(DARWIN_QTKIT)
+
addFileExtensionAlias("mov", "qt");
addFileExtensionAlias("live", "qt");
addFileExtensionAlias("mpg", "qt");
View
6 src/osgPlugins/CMakeLists.txt
@@ -218,6 +218,12 @@ IF(QUICKTIME_FOUND)
ADD_SUBDIRECTORY(quicktime)
ENDIF()
+IF(QTKIT_FOUND)
+ ADD_SUBDIRECTORY(QTKit)
+ENDIF()
+
+
+
IF(FREETYPE_FOUND)
ADD_SUBDIRECTORY(freetype)
ENDIF()
View
17 src/osgPlugins/QTKit/CMakeLists.txt
@@ -0,0 +1,17 @@
+INCLUDE_DIRECTORIES( ${QTKIT_INCLUDE_DIR} )
+
+SET(TARGET_SRC
+ ReaderWriterQTKit.mm
+)
+
+SET(TARGET_LIBRARIES_VARS QTKIT_LIBRARY COCOA_LIBRARY QUICKTIME_LIBRARY COREVIDEO_LIBRARY)
+SET(TARGET_ADDED_LIBRARIES osgViewer )
+
+
+IF(CMAKE_COMPILER_IS_GNUCXX)
+ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations")
+ENDIF()
+
+
+#### end var setup ###
+SETUP_PLUGIN(QTKit)
View
741 src/osgPlugins/QTKit/ReaderWriterQTKit.mm
@@ -0,0 +1,741 @@
+// Modern QTKit/Core Video plugin for OSG.
+// Eric Wing
+
+#include <osg/ImageStream>
+#include <osg/Notify>
+#include <osg/Geode>
+
+#include <osgDB/Registry>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+
+#import <Foundation/Foundation.h>
+#import <QTKit/QTKit.h>
+
+// Optimization to share the CoreVideo pixelbuffer with the ImageStream data avoiding memcpy's.
+// Risks are that we are more exposed to race conditions when we update the image
+// since Core Video updates happen on a background thread.
+#define SHARE_CVPIXELBUFFER 1
+
+/* Implementation notes:
+ * The first problem is that the whole OSG design is centered around pumping
+ * osg::ImageStreams through the system.
+ * But Core Video actual can give us OpenGL textures that are ready-to-go.
+ * (Core Video can also give us PBO's, but there seems to be an issue elsewhere
+ * in OSG w.r.t. PBOs on OS X.)
+ * OSG is not friendly with dealing with textures created from the outside.
+ * In the interests of getting something working (but not optimal),
+ * I use the Core Video PixelBuffer APIs to fetch data to main memory to provide osg::ImageStream
+ * with data let it upload the texture data. What a waste!
+ *
+ * The second problem is that Apple still hasn't updated their QTKit/Core Video glue API
+ * to be 64-bit ready as of Snow Leopard.
+ * This means that this plugin only works in 32-bit until Apple gets their act together.
+ * We also can't activate the Quicktime X optimized playback renderer.
+ *
+ * The third problem is that OSG makes some really bad assumptions about when the movie size (width,height)
+ * becomes available. Particularly with live streams, movie sizes are allowed to change inflight and
+ * you may not get a valid movie size until after you initially start playing the stream. OSG assumes
+ * you have the correct movie size right on open and that it will never change.
+ *
+ * The forth problem is that there are so many plugins competing for the same movie types
+ * I don't know what's going on and it seems rather rigid being a compile time things we must set.
+ *
+ * Also note for audio, this plugin completely ignores/bypasses the AudioStream class.
+ */
+
+static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef display_link,
+ const CVTimeStamp* in_now,
+ const CVTimeStamp* in_output_time,
+ CVOptionFlags flags_in,
+ CVOptionFlags* flags_out,
+ void* user_data);
+//@class MovieNotificationHandler;
+
+namespace osgQTKit
+{
+ class QTKitImageStream;
+}
+
+@interface MovieNotificationHandler : NSObject
+{
+ osgQTKit::QTKitImageStream* imageStream;
+}
+
+- (void) setImageStream:(osgQTKit::QTKitImageStream*)image_stream;
+- (void) movieNaturalSizeDidChange:(NSNotification*)the_notification;
+- (void) movieLoadStateDidChange:(NSNotification*)the_notification;
+- (void) movieDidEnd:(NSNotification*)the_notification;
+
+@end
+
+
+namespace osgQTKit
+{
+
+class QTKitImageStream : public osg::ImageStream
+{
+ public:
+ QTKitImageStream():
+ ImageStream(),
+ displayLink(NULL),
+#if SHARE_CVPIXELBUFFER
+ currentSwapFrameIndex(0),
+#else
+ currentFrame(NULL),
+#endif
+ qtMovie(nil),
+ pixelBufferContextForQTOpenGL(NULL)
+ {
+#if SHARE_CVPIXELBUFFER
+ swapFrame[0] = NULL;
+ swapFrame[1] = NULL;
+#endif
+ setOrigin(osg::Image::TOP_LEFT);
+ initDisplayLink();
+// Class movie_notification_class = NSClassFromString(@"MovieNotificationHandler");
+// movieNotificationHandler = [[movie_notification_class alloc] init];
+ movieNotificationHandler = [[MovieNotificationHandler alloc] init];
+ [movieNotificationHandler setImageStream:this];
+ // for optional garbage collection dancing
+ CFRetain(movieNotificationHandler);
+ [movieNotificationHandler release];
+ }
+
+ /** Copy constructor using CopyOp to manage deep vs shallow copy. */
+ QTKitImageStream(const QTKitImageStream& image,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY):
+ ImageStream(image,copyop) {}
+
+ // Core Video requires the CGLContent and CGLPixelFormat
+ // to setup the displaylink
+
+ void initDisplayLink()
+ {
+ NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
+ if (NULL != displayLink)
+ {
+ if(CVDisplayLinkIsRunning(displayLink))
+ {
+ CVDisplayLinkStop(displayLink);
+ }
+ CVDisplayLinkRelease(displayLink);
+ displayLink = NULL;
+ }
+
+ // Because we don't have easy access to CGDisplayIDs, we create a displaylink which
+ // will work with all the active displays. This is the most flexible option, though maybe
+ // not always the fastest.
+ CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
+ if (NULL != displayLink)
+ {
+ // set the renderer output callback function
+ CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, this);
+ }
+
+ [autorelease_pool drain];
+ }
+
+ META_Object(osgQTKit,QTKitImageStream);
+
+ void setVolume(float the_volume)
+ {
+ [qtMovie setVolume:the_volume];
+ }
+
+ float getVolume() const
+ {
+ return [qtMovie volume];
+ }
+
+ bool open(const std::string& file_name)
+ {
+ NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
+ NSString* ns_string = [NSString stringWithUTF8String:file_name.c_str()];
+ NSError* the_error = nil;
+
+ if(nil != qtMovie)
+ {
+ // Do we allow this?
+ // Don't return without cleaning up autorelease pool
+ // shutdown displaylink if this is allowed
+ // CFRelease(objCData->qtMovie);
+ [autorelease_pool drain];
+ return false;
+ }
+
+
+ // Use QTMovieOpenForPlaybackAttribute to activate Quicktime X
+ // (Disabled because we can't use this while some APIs we use are 32-bit)
+ NSDictionary* movie_attributes =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ ns_string, QTMovieFileNameAttribute,
+// [NSNumber numberWithBool:YES], QTMovieLoopsAttribute,
+// [NSNumber numberWithBool:YES], QTMovieOpenForPlaybackAttribute,
+ nil];
+ qtMovie = [[QTMovie alloc] initWithAttributes:movie_attributes error:&the_error];
+ if(nil != qtMovie)
+ {
+ // For garbage collection, we need to make sure to hold the reference
+ // This code is designed to work for both modes.
+ CFRetain(qtMovie);
+ [qtMovie release];
+ }
+ else
+ {
+// NSLog(@"Failed to open file: %@, %@", ns_string, [the_error localizedDescription]);
+ OSG_WARN<<"Failed to open file: " << file_name << std::endl;
+
+ }
+
+ [[NSNotificationCenter defaultCenter] addObserver:movieNotificationHandler
+ selector:@selector(movieNaturalSizeDidChange:)
+ name:QTMovieNaturalSizeDidChangeNotification
+ object:qtMovie];
+
+ [[NSNotificationCenter defaultCenter] addObserver:movieNotificationHandler
+ selector:@selector(movieLoadStateDidChange:)
+ name:QTMovieLoadStateDidChangeNotification
+ object:qtMovie];
+
+ [[NSNotificationCenter defaultCenter] addObserver:movieNotificationHandler
+ selector:@selector(movieDidEnd:)
+ name:QTMovieDidEndNotification
+ object:qtMovie];
+
+ [autorelease_pool drain];
+ return true;
+
+
+ }
+
+
+ virtual void play()
+ {
+ NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
+
+ _status=PLAYING;
+ if(NULL == pixelBufferContextForQTOpenGL)
+ {
+ // This isn't guaranteed to yield a valid size.
+ // We are supposed to wait for a callback after the video stream connection has been established.
+ NSSize movie_size = [[qtMovie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
+// NSLog(@"movie_size=%f, %f", movie_size.width, movie_size.height);
+ NSDictionary* pixel_buffer_attributes =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+#if __BIG_ENDIAN__
+ [NSNumber numberWithInteger:k32ARGBPixelFormat], kCVPixelBufferPixelFormatTypeKey,
+#else
+ [NSNumber numberWithInteger:k32BGRAPixelFormat], kCVPixelBufferPixelFormatTypeKey,
+#endif
+ // Seems that Core Video will figure out the size automatically.
+ // Probably better that way since our values may be wrong.
+// [NSNumber numberWithFloat:movie_size.width], kCVPixelBufferWidthKey,
+// [NSNumber numberWithFloat:movie_size.height], kCVPixelBufferHeightKey,
+ [NSNumber numberWithInteger:1], kCVPixelBufferBytesPerRowAlignmentKey,
+ [NSNumber numberWithBool:YES], kCVPixelBufferOpenGLCompatibilityKey,
+ nil
+ ];
+ NSDictionary* visual_context_options =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ pixel_buffer_attributes, kQTVisualContextPixelBufferAttributesKey,
+ nil
+ ];
+
+ OSStatus the_error = QTPixelBufferContextCreate(
+ kCFAllocatorDefault, // an allocator to Create functions
+ (CFDictionaryRef)visual_context_options, // a CF Dictionary of attributes
+ &pixelBufferContextForQTOpenGL);
+ if(noErr != the_error)
+ {
+ NSLog(@"Error calling QTPixelBufferContextCreate: os_status=%d, pixelBufferContextForQTOpenGL=%x", the_error, pixelBufferContextForQTOpenGL);
+
+ }
+ // Because of bad osgmovie assumptions, we need to set the size now even though we may not have correct information.
+#if SHARE_CVPIXELBUFFER
+ setImage((int)movie_size.width,(int)movie_size.height,1,
+ GL_RGBA8,
+ GL_BGRA,
+ GL_UNSIGNED_INT_8_8_8_8_REV,
+ NULL,
+ osg::Image::NO_DELETE,
+ 1);
+#else
+ allocateImage((int)movie_size.width,(int)movie_size.height,1,GL_BGRA,GL_UNSIGNED_INT_8_8_8_8_REV,1);
+ setInternalTextureFormat(GL_RGBA8);
+#endif
+
+ SetMovieVisualContext([qtMovie quickTimeMovie], pixelBufferContextForQTOpenGL);
+
+ }
+
+
+
+ if(!CVDisplayLinkIsRunning(displayLink))
+ {
+ CVReturn err_flag = CVDisplayLinkStart(displayLink);
+ if(kCVReturnSuccess != err_flag)
+ {
+ NSLog(@"Error CVDisplayLinkStart()");
+ }
+
+
+ [qtMovie play];
+ }
+ else
+ {
+// NSLog(@"Alreadying playing");
+ }
+
+
+
+ [autorelease_pool drain];
+
+ }
+
+ virtual void pause()
+ {
+ NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
+ _status=PAUSED;
+ if(CVDisplayLinkIsRunning(displayLink))
+ {
+ CVDisplayLinkStop(displayLink);
+ [qtMovie stop];
+ }
+ [autorelease_pool drain];
+ }
+
+ virtual void rewind()
+ {
+ NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
+ _status=REWINDING; // seriously? This means that the movie will continue to be in this state until played/paused
+ [qtMovie gotoBeginning];
+ [autorelease_pool drain];
+ }
+
+ // OSG Documentation doesn't say what time is.
+ // Is it an absolute time in the movie, or an offset to move based on the current time (which can be negative)?
+ // And what are the units? seconds? milliseconds, minutes?
+ virtual void seek(double seek_time)
+ {
+
+ /*
+ http://developer.apple.com/mac/library/technotes/tn2005/tn2138.html
+ QTTime oldTime = [qtMovie currentTime];
+ QTTime incTime = QTTimeFromString( @"00:02:00.00" );
+ QTTime newTime = QTTimeIncrement( oldTime, incTime );
+
+ NSLog( QTStringFromTime( oldTime ) );
+ NSLog( QTStringFromTime( incTime ) );
+ NSLog( QTStringFromTime( newtime ) );
+ I get the following results:
+
+ 0:00:00:00.00/48000
+ 0:00:00:00.00/1000000
+ 0:00:00:00.00/1000000
+ I have also tried setting the time string to @"0:00:02:00.00", @"0:0:2:0.0", and other variations. No luck.
+
+ What am I doing wrong?
+
+ A: You'll notice the following comment in QTTime.h:
+
+ // ,,,dd:hh:mm:ss.ff/ts
+ which translates into:
+
+ days:hours:minutes:seconds:frames/timescale
+ So you should try a string like:
+
+ QTTime incTime = QTTimeFromString( @"00:00:02:00.00/600" );
+ NSLog( QTStringFromTime( incTime ) );
+ */
+
+ }
+
+ virtual void quit()
+ {
+ close();
+ }
+
+ virtual void setTimeMultiplier(double the_rate)
+ {
+ NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
+ [qtMovie setRate:the_rate];
+ [autorelease_pool drain];
+ }
+ virtual double getTimeMultiplier() const
+ {
+ NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
+ float the_rate = [qtMovie rate];
+ [autorelease_pool drain];
+ return the_rate;
+ }
+
+ CVReturn handleCoreVideoCallback(
+ CVDisplayLinkRef display_link,
+ const CVTimeStamp* in_now,
+ const CVTimeStamp* in_output_time,
+ CVOptionFlags flags_in,
+ CVOptionFlags* flags_out,
+ void* user_data
+ )
+ {
+// NSLog(@"In handleCoreVideoCallback");
+ if(NULL == pixelBufferContextForQTOpenGL)
+ {
+// NSLog(@"pixelBufferContextForQTOpenGL is NULL");
+ return kCVReturnSuccess;
+ }
+ // CoreVideo callbacks happen on a secondary thread.
+ // So we need a new autorelease pool for this thread.
+ NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init];
+ // check for new frame
+
+
+ /*
+ * Notes: The SHARE_CVPIXELBUFFER stuff reuses the same memory allocated by Core Video
+ * for the osg::Image data. This avoids extra memcpy's.
+ * FIXME: This probably needs locking. What is the locking model for osg::Image?
+ * Since Core Video operates on a high priority background thread, it is possible
+ * that the osg::Image could be utilized while we are updating with new frame data.
+ * Experimentally, I have not gotten any crashes, but I have noticed flickering
+ * in my original implementation where I would first set the osg::Image data to NULL
+ * before releasing the CVPixelBuffer. To avoid the flickering, I have now implemented
+ * a double-buffering technique where I immediately provide the new data and then
+ * clean up after the swap.
+ */
+ if(QTVisualContextIsNewImageAvailable(pixelBufferContextForQTOpenGL, in_output_time))
+ {
+#if SHARE_CVPIXELBUFFER
+ size_t previous_swap_frame_index = currentSwapFrameIndex;
+
+ // flip the active swap buffer
+ if(0 == currentSwapFrameIndex)
+ {
+ currentSwapFrameIndex = 1;
+ }
+ else
+ {
+ currentSwapFrameIndex = 0;
+ }
+
+ OSStatus error_status = QTVisualContextCopyImageForTime(pixelBufferContextForQTOpenGL, NULL, in_output_time, &swapFrame[currentSwapFrameIndex]);
+ // the above call may produce a null frame so check for this first
+ // if we have a frame, then draw it
+ if ((noErr == error_status) && (NULL != swapFrame[currentSwapFrameIndex]))
+ {
+ size_t buffer_width = CVPixelBufferGetWidth(swapFrame[currentSwapFrameIndex]);
+ size_t buffer_height = CVPixelBufferGetHeight(swapFrame[currentSwapFrameIndex]);
+// NSLog(@"CVPixelBuffer w=%d, h=%d", buffer_width, buffer_height);
+ // buffer_width = 480;
+ // buffer_height = 320;
+
+ CVPixelBufferLockBaseAddress( swapFrame[currentSwapFrameIndex], kCVPixelBufferLock_ReadOnly );
+
+ void* raw_pixel_data = CVPixelBufferGetBaseAddress(swapFrame[currentSwapFrameIndex]);
+
+ 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);
+ // seems to have no effect. Flip image the hard way
+ // setOrigin(osg::Image::TOP_LEFT);
+ // flipVertical();
+
+
+ CVPixelBufferUnlockBaseAddress( swapFrame[currentSwapFrameIndex], 0 );
+ }
+
+ // Now clean up previous frame
+ // Release the previous frame. (This is safe to call even if it is NULL.)
+ CVPixelBufferRelease(swapFrame[previous_swap_frame_index]);
+ swapFrame[previous_swap_frame_index] = NULL;
+
+#else
+ // if we have a previous frame release it
+ if (NULL != currentFrame)
+ {
+ CVPixelBufferRelease(currentFrame);
+ currentFrame = NULL;
+ }
+
+ // get a "frame" (image buffer) from the Visual Context, indexed by the provided time
+ OSStatus error_status = QTVisualContextCopyImageForTime(pixelBufferContextForQTOpenGL, NULL, in_output_time, &currentFrame);
+ // the above call may produce a null frame so check for this first
+ // if we have a frame, then draw it
+ if ((noErr == error_status) && (NULL != currentFrame))
+ {
+ size_t buffer_width = CVPixelBufferGetWidth(currentFrame);
+ size_t buffer_height = CVPixelBufferGetHeight(currentFrame);
+ // NSLog(@"CVPixelBuffer w=%d, h=%d", buffer_width, buffer_height);
+ // buffer_width = 480;
+ // buffer_height = 320;
+
+ CVPixelBufferLockBaseAddress( currentFrame, kCVPixelBufferLock_ReadOnly );
+
+ void* raw_pixel_data = CVPixelBufferGetBaseAddress(currentFrame);
+
+
+ /*
+ NSLog(@"CVPixelBufferGetDataSize(currentFrame)=%d", CVPixelBufferGetDataSize(currentFrame));
+ NSLog(@"CVPixelBufferIsPlanar(currentFrame)=%d", CVPixelBufferIsPlanar(currentFrame));
+ NSLog(@"CVPixelBufferGetBytesPerRow(currentFrame)=%d", CVPixelBufferGetBytesPerRow(currentFrame));
+ */
+
+ // Don't understand why CVPixelBufferGetDataSize returns a slightly bigger size
+ // e.g. for a 480x320 movie, it is 32-bytes larger than 480*320*4
+ // memcpy(data(),raw_pixel_data,CVPixelBufferGetDataSize(currentFrame));
+ memcpy(data(),raw_pixel_data,buffer_width*buffer_height*4);
+
+ // flipVertical();
+ dirty();
+ CVPixelBufferUnlockBaseAddress( currentFrame, 0 );
+ }
+#endif
+ } // end QTVisualContextIsNewImageAvailable()
+
+
+ [autorelease_pool drain];
+ return kCVReturnSuccess;
+ }
+
+ // TODO: OSG really needs some kind of notification callback for this so your OSG can find out that
+ // the movie size has changed.
+ void handleMovieNaturalSizeDidChange()
+ {
+// NSSize movie_size = [[qtMovie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
+// NSLog(@"handleMovieNaturalSizeDidChange=%f, %f", movie_size.width, movie_size.height);
+ pause();
+ QTVisualContextRelease(pixelBufferContextForQTOpenGL);
+ pixelBufferContextForQTOpenGL = NULL;
+ play();
+ }
+
+ // Untested: I think the important case to handle is usually live streaming and there is underrun.
+ void handleMovieLoadStateDidChange()
+ {
+// NSLog(@"handleMovieLoadStateDidChange");
+ if( (PLAYING == _status) && ([qtMovie rate] == 0.0) ) // if should be playing, but not playing
+ {
+// NSLog(@"not playing");
+ if([[qtMovie attributeForKey:QTMovieLoadStateAttribute] longValue] >= kMovieLoadStatePlaythroughOK)
+ {
+// NSLog(@"handleMovieLoadStateDidChangeCallback play");
+
+ [qtMovie play];
+ }
+ }
+ else
+ {
+// NSLog(@"playing");
+ }
+ }
+
+ void handleMovieDidEnd()
+ {
+ pause();
+ QTVisualContextRelease(pixelBufferContextForQTOpenGL);
+ pixelBufferContextForQTOpenGL = NULL;
+ // should I rewind? What is the expected behavior?
+ }
+
+ protected:
+ CVDisplayLinkRef displayLink;
+#if SHARE_CVPIXELBUFFER
+ CVPixelBufferRef swapFrame[2];
+ size_t currentSwapFrameIndex;
+#else
+ CVPixelBufferRef currentFrame;
+#endif
+ QTMovie* qtMovie;
+ QTVisualContextRef pixelBufferContextForQTOpenGL;
+// id movieNotificationHandler;
+ MovieNotificationHandler* movieNotificationHandler;
+
+ virtual ~QTKitImageStream()
+ {
+ close();
+ if (NULL != displayLink)
+ {
+ if(CVDisplayLinkIsRunning(displayLink))
+ {
+ CVDisplayLinkStop(displayLink);
+ }
+ CVDisplayLinkRelease(displayLink);
+ displayLink = NULL;
+ }
+ CFRelease(movieNotificationHandler);
+ movieNotificationHandler = nil;
+ }
+
+ void close()
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:movieNotificationHandler
+ name:QTMovieLoadStateDidChangeNotification object:qtMovie];
+ [[NSNotificationCenter defaultCenter] removeObserver:movieNotificationHandler
+ name:QTMovieNaturalSizeDidChangeNotification object:qtMovie];
+ [[NSNotificationCenter defaultCenter] removeObserver:movieNotificationHandler
+ name:QTMovieDidEndNotification object:qtMovie];
+
+ if(CVDisplayLinkIsRunning(displayLink))
+ {
+ CVDisplayLinkStop(displayLink);
+ }
+
+ QTVisualContextRelease(pixelBufferContextForQTOpenGL);
+ pixelBufferContextForQTOpenGL = NULL;
+#if SHARE_CVPIXELBUFFER
+ CVPixelBufferRelease(swapFrame[0]);
+ swapFrame[0] = NULL;
+ CVPixelBufferRelease(swapFrame[1]);
+ swapFrame[1] = NULL;
+ currentSwapFrameIndex = 0;
+#else
+ CVPixelBufferRelease(currentFrame);
+ currentFrame = NULL;
+#endif
+
+ if(nil != qtMovie)
+ {
+ CFRelease(qtMovie);
+ qtMovie = nil;
+ }
+ }
+ virtual void applyLoopingMode()
+ {
+ if(NO_LOOPING == _loopingMode)
+ {
+ [qtMovie setAttribute:[NSNumber numberWithBool:NO] forKey:QTMovieLoopsAttribute];
+ }
+ else
+ {
+ [qtMovie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieLoopsAttribute];
+ }
+ }
+
+
+
+};
+
+
+}
+
+static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef display_link,
+ const CVTimeStamp* in_now,
+ const CVTimeStamp* in_output_time,
+ CVOptionFlags flags_in,
+ CVOptionFlags* flags_out,
+ void* user_data)
+{
+ if(NULL != user_data)
+ {
+ osgQTKit::QTKitImageStream* qtkit_image_stream = reinterpret_cast<osgQTKit::QTKitImageStream*>(user_data);
+ return qtkit_image_stream->handleCoreVideoCallback(display_link, in_now, in_output_time, flags_in, flags_out, NULL);
+ }
+ return kCVReturnSuccess;
+}
+
+class ReaderWriterQTKit : public osgDB::ReaderWriter
+{
+ public:
+
+ ReaderWriterQTKit()
+ {
+ supportsExtension("mov","Quicktime movie format");
+ supportsExtension("mpg","Mpeg movie format");
+ supportsExtension("mp4","Mpeg movie format");
+ supportsExtension("mpv","Mpeg movie format");
+ supportsExtension("mpeg","Mpeg movie format");
+
+ // only with Perian
+ supportsExtension("avi","");
+ supportsExtension("xvid","");
+ // only with Flip4Mac
+ supportsExtension("wmv","");
+
+ }
+ 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,"avi") ||
+ osgDB::equalCaseInsensitive(extension,"xvid") ||
+ osgDB::equalCaseInsensitive(extension,"wmv");
+
+ }
+
+ virtual ~ReaderWriterQTKit()
+ {
+ OSG_INFO<<"~ReaderWriterQTKit()"<<std::endl;
+ }
+
+ virtual const char* className() const { return "QTKit ImageStream Reader"; }
+
+ virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) const
+ {
+ std::string ext = osgDB::getLowerCaseFileExtension(file);
+ if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
+
+ std::string fileName;
+ if (ext=="QTKit")
+ {
+ fileName = osgDB::findDataFile( osgDB::getNameLessExtension(file), options);
+ OSG_INFO<<"QTKit stipped filename = "<<fileName<<std::endl;
+ }
+ else
+ {
+ fileName = osgDB::findDataFile( file, options );
+ if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
+ }
+
+ OSG_INFO<<"ReaderWriterQTKit::readImage "<< file<< std::endl;
+
+ osg::ref_ptr<osgQTKit::QTKitImageStream> imageStream = new osgQTKit::QTKitImageStream();
+
+ if (!imageStream->open(fileName)) return ReadResult::FILE_NOT_HANDLED;
+
+ return imageStream.release();
+ }
+
+ protected:
+
+
+};
+
+
+@implementation MovieNotificationHandler
+
+- (void) setImageStream:(osgQTKit::QTKitImageStream*)image_stream
+{
+ imageStream = image_stream;
+}
+
+// I need to verify if this is being called back on a non-main thread.
+// My initial observation led me to think it was being called on a background thread,
+// but it could be that I was just confused which thread was the main thread since
+// CoreVideo was doing a bunch of stuff on its own background thread.
+- (void) movieNaturalSizeDidChange:(NSNotification*)the_notification
+{
+ imageStream->handleMovieNaturalSizeDidChange();
+}
+
+- (void) movieLoadStateDidChange:(NSNotification*)the_notification
+{
+ imageStream->handleMovieLoadStateDidChange();
+}
+
+- (void) movieDidEnd:(NSNotification*)the_notification
+{
+ imageStream->handleMovieDidEnd();
+}
+@end
+
+
+// now register with Registry to instantiate the above
+// reader/writer.
+REGISTER_OSGPLUGIN(QTKit, ReaderWriterQTKit)
Please sign in to comment.
Something went wrong with that request. Please try again.