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

This commit is contained in:
Robert Osfield
2010-09-24 12:59:37 +00:00
parent c006c75615
commit 1224836664
7 changed files with 872 additions and 10 deletions

View File

@@ -512,6 +512,8 @@ IF(NOT APPLE)
ELSE()
FIND_PACKAGE(QuickTime)
FIND_PACKAGE(QTKit)
FIND_PACKAGE(CoreVideo)
ENDIF()
################################################################################

View File

@@ -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 File

@@ -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 File

@@ -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 File

@@ -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 File

@@ -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 File

@@ -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)