1// Modern QTKit/Core Video plugin for OSG. 2// Eric Wing 3 4#include <osg/ImageStream> 5#include <osg/Notify> 6#include <osg/Geode> 7 8#include <osgDB/Registry> 9#include <osgDB/FileNameUtils> 10#include <osgDB/FileUtils> 11 12#import <Foundation/Foundation.h> 13#import <QTKit/QTKit.h> 14 15// Optimization to share the CoreVideo pixelbuffer with the ImageStream data avoiding memcpy's. 16// Risks are that we are more exposed to race conditions when we update the image 17// since Core Video updates happen on a background thread. 18#define SHARE_CVPIXELBUFFER 1 19 20/* Implementation notes: 21 * The first problem is that the whole OSG design is centered around pumping 22 * osg::ImageStreams through the system. 23 * But Core Video actual can give us OpenGL textures that are ready-to-go. 24 * (Core Video can also give us PBO's, but there seems to be an issue elsewhere 25 * in OSG w.r.t. PBOs on OS X.) 26 * OSG is not friendly with dealing with textures created from the outside. 27 * In the interests of getting something working (but not optimal), 28 * I use the Core Video PixelBuffer APIs to fetch data to main memory to provide osg::ImageStream 29 * with data let it upload the texture data. What a waste! 30 * 31 * The second problem is that Apple still hasn't updated their QTKit/Core Video glue API 32 * to be 64-bit ready as of Snow Leopard. 33 * This means that this plugin only works in 32-bit until Apple gets their act together. 34 * We also can't activate the Quicktime X optimized playback renderer. 35 * 36 * The third problem is that OSG makes some really bad assumptions about when the movie size (width,height) 37 * becomes available. Particularly with live streams, movie sizes are allowed to change inflight and 38 * you may not get a valid movie size until after you initially start playing the stream. OSG assumes 39 * you have the correct movie size right on open and that it will never change. 40 * 41 * The forth problem is that there are so many plugins competing for the same movie types 42 * I don't know what's going on and it seems rather rigid being a compile time things we must set. 43 * 44 * Also note for audio, this plugin completely ignores/bypasses the AudioStream class. 45 */ 46 47static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef display_link, 48 const CVTimeStamp* in_now, 49 const CVTimeStamp* in_output_time, 50 CVOptionFlags flags_in, 51 CVOptionFlags* flags_out, 52 void* user_data); 53//@class MovieNotificationHandler; 54 55namespace osgQTKit 56{ 57 class QTKitImageStream; 58} 59 60@interface MovieNotificationHandler : NSObject 61{ 62 osgQTKit::QTKitImageStream* imageStream; 63} 64 65- (void) setImageStream:(osgQTKit::QTKitImageStream*)image_stream; 66- (void) movieNaturalSizeDidChange:(NSNotification*)the_notification; 67- (void) movieLoadStateDidChange:(NSNotification*)the_notification; 68- (void) movieDidEnd:(NSNotification*)the_notification; 69 70@end 71 72 73namespace osgQTKit 74{ 75 76class QTKitImageStream : public osg::ImageStream 77{ 78 public: 79 QTKitImageStream(): 80 ImageStream(), 81 displayLink(NULL), 82#if SHARE_CVPIXELBUFFER 83 currentSwapFrameIndex(0), 84#else 85 currentFrame(NULL), 86#endif 87 qtMovie(nil), 88 pixelBufferContextForQTOpenGL(NULL) 89 { 90#if SHARE_CVPIXELBUFFER 91 swapFrame[0] = NULL; 92 swapFrame[1] = NULL; 93#endif 94 setOrigin(osg::Image::TOP_LEFT); 95 initDisplayLink(); 96// Class movie_notification_class = NSClassFromString(@"MovieNotificationHandler"); 97// movieNotificationHandler = [[movie_notification_class alloc] init]; 98 movieNotificationHandler = [[MovieNotificationHandler alloc] init]; 99 [movieNotificationHandler setImageStream:this]; 100 // for optional garbage collection dancing 101 CFRetain(movieNotificationHandler); 102 [movieNotificationHandler release]; 103 } 104 105 /** Copy constructor using CopyOp to manage deep vs shallow copy. */ 106 QTKitImageStream(const QTKitImageStream& image,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY): 107 ImageStream(image,copyop) {} 108 109 // Core Video requires the CGLContent and CGLPixelFormat 110 // to setup the displaylink 111 112 void initDisplayLink() 113 { 114 NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init]; 115 if (NULL != displayLink) 116 { 117 if(CVDisplayLinkIsRunning(displayLink)) 118 { 119 CVDisplayLinkStop(displayLink); 120 } 121 CVDisplayLinkRelease(displayLink); 122 displayLink = NULL; 123 } 124 125 // Because we don't have easy access to CGDisplayIDs, we create a displaylink which 126 // will work with all the active displays. This is the most flexible option, though maybe 127 // not always the fastest. 128 CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); 129 if (NULL != displayLink) 130 { 131 // set the renderer output callback function 132 CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, this); 133 } 134 135 [autorelease_pool drain]; 136 } 137 138 META_Object(osgQTKit,QTKitImageStream); 139 140 void setVolume(float the_volume) 141 { 142 [qtMovie setVolume:the_volume]; 143 } 144 145 float getVolume() const 146 { 147 return [qtMovie volume]; 148 } 149 150 bool open(const std::string& file_name) 151 { 152 NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init]; 153 NSString* ns_string = [NSString stringWithUTF8String:file_name.c_str()]; 154 NSError* the_error = nil; 155 156 if(nil != qtMovie) 157 { 158 // Do we allow this? 159 // Don't return without cleaning up autorelease pool 160 // shutdown displaylink if this is allowed 161 // CFRelease(objCData->qtMovie); 162 [autorelease_pool drain]; 163 return false; 164 } 165 166 167 // Use QTMovieOpenForPlaybackAttribute to activate Quicktime X 168 // (Disabled because we can't use this while some APIs we use are 32-bit) 169 NSDictionary* movie_attributes = 170 [NSDictionary dictionaryWithObjectsAndKeys: 171 ns_string, QTMovieFileNameAttribute, 172// [NSNumber numberWithBool:YES], QTMovieLoopsAttribute, 173// [NSNumber numberWithBool:YES], QTMovieOpenForPlaybackAttribute, 174 nil]; 175 qtMovie = [[QTMovie alloc] initWithAttributes:movie_attributes error:&the_error]; 176 if(nil != qtMovie) 177 { 178 // For garbage collection, we need to make sure to hold the reference 179 // This code is designed to work for both modes. 180 CFRetain(qtMovie); 181 [qtMovie release]; 182 } 183 else 184 { 185// NSLog(@"Failed to open file: %@, %@", ns_string, [the_error localizedDescription]); 186 OSG_WARN<<"Failed to open file: " << file_name << std::endl; 187 188 } 189 190 [[NSNotificationCenter defaultCenter] addObserver:movieNotificationHandler 191 selector:@selector(movieNaturalSizeDidChange:) 192#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) 193 name:QTMovieNaturalSizeDidChangeNotification 194#else 195 name:QTMovieSizeDidChangeNotification 196#endif 197 object:qtMovie]; 198 199 [[NSNotificationCenter defaultCenter] addObserver:movieNotificationHandler 200 selector:@selector(movieLoadStateDidChange:) 201 name:QTMovieLoadStateDidChangeNotification 202 object:qtMovie]; 203 204 [[NSNotificationCenter defaultCenter] addObserver:movieNotificationHandler 205 selector:@selector(movieDidEnd:) 206 name:QTMovieDidEndNotification 207 object:qtMovie]; 208 209 [autorelease_pool drain]; 210 return true; 211 212 213 } 214 215 216 virtual void play() 217 { 218 NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init]; 219 220 _status=PLAYING; 221 if(NULL == pixelBufferContextForQTOpenGL) 222 { 223 // This isn't guaranteed to yield a valid size. 224 // We are supposed to wait for a callback after the video stream connection has been established. 225 NSSize movie_size = [[qtMovie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]; 226// NSLog(@"movie_size=%f, %f", movie_size.width, movie_size.height); 227 NSDictionary* pixel_buffer_attributes = 228 [NSDictionary dictionaryWithObjectsAndKeys: 229#if __BIG_ENDIAN__ 230 [NSNumber numberWithInteger:k32ARGBPixelFormat], kCVPixelBufferPixelFormatTypeKey, 231#else 232 [NSNumber numberWithInteger:k32BGRAPixelFormat], kCVPixelBufferPixelFormatTypeKey, 233#endif 234 // Seems that Core Video will figure out the size automatically. 235 // Probably better that way since our values may be wrong. 236// [NSNumber numberWithFloat:movie_size.width], kCVPixelBufferWidthKey, 237// [NSNumber numberWithFloat:movie_size.height], kCVPixelBufferHeightKey, 238 [NSNumber numberWithInteger:1], kCVPixelBufferBytesPerRowAlignmentKey, 239 [NSNumber numberWithBool:YES], kCVPixelBufferOpenGLCompatibilityKey, 240 nil 241 ]; 242 NSDictionary* visual_context_options = 243 [NSDictionary dictionaryWithObjectsAndKeys: 244 pixel_buffer_attributes, kQTVisualContextPixelBufferAttributesKey, 245 nil 246 ]; 247 248 OSStatus the_error = QTPixelBufferContextCreate( 249 kCFAllocatorDefault, // an allocator to Create functions 250 (CFDictionaryRef)visual_context_options, // a CF Dictionary of attributes 251 &pixelBufferContextForQTOpenGL); 252 if(noErr != the_error) 253 { 254 NSLog(@"Error calling QTPixelBufferContextCreate: os_status=%d, pixelBufferContextForQTOpenGL=%x", the_error, pixelBufferContextForQTOpenGL); 255 256 } 257 // Because of bad osgmovie assumptions, we need to set the size now even though we may not have correct information. 258#if SHARE_CVPIXELBUFFER 259 setImage((int)movie_size.width,(int)movie_size.height,1, 260 GL_RGBA8, 261 GL_BGRA, 262 GL_UNSIGNED_INT_8_8_8_8_REV, 263 NULL, 264 osg::Image::NO_DELETE, 265 1); 266#else 267 allocateImage((int)movie_size.width,(int)movie_size.height,1,GL_BGRA,GL_UNSIGNED_INT_8_8_8_8_REV,1); 268 setInternalTextureFormat(GL_RGBA8); 269#endif 270 271 SetMovieVisualContext([qtMovie quickTimeMovie], pixelBufferContextForQTOpenGL); 272 273 } 274 275 276 277 if(!CVDisplayLinkIsRunning(displayLink)) 278 { 279 CVReturn err_flag = CVDisplayLinkStart(displayLink); 280 if(kCVReturnSuccess != err_flag) 281 { 282 NSLog(@"Error CVDisplayLinkStart()"); 283 } 284 285 286 [qtMovie play]; 287 } 288 else 289 { 290// NSLog(@"Alreadying playing"); 291 } 292 293 294 295 [autorelease_pool drain]; 296 297 } 298 299 virtual void pause() 300 { 301 NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init]; 302 _status=PAUSED; 303 if(CVDisplayLinkIsRunning(displayLink)) 304 { 305 CVDisplayLinkStop(displayLink); 306 [qtMovie stop]; 307 } 308 [autorelease_pool drain]; 309 } 310 311 virtual void rewind() 312 { 313 NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init]; 314 _status=REWINDING; // seriously? This means that the movie will continue to be in this state until played/paused 315 [qtMovie gotoBeginning]; 316 [autorelease_pool drain]; 317 } 318 319 // OSG Documentation doesn't say what time is. 320 // Is it an absolute time in the movie, or an offset to move based on the current time (which can be negative)? 321 // And what are the units? seconds? milliseconds, minutes? 322 virtual void seek(double seek_time) 323 { 324 325 /* 326 http://developer.apple.com/mac/library/technotes/tn2005/tn2138.html 327 QTTime oldTime = [qtMovie currentTime]; 328 QTTime incTime = QTTimeFromString( @"00:02:00.00" ); 329 QTTime newTime = QTTimeIncrement( oldTime, incTime ); 330 331 NSLog( QTStringFromTime( oldTime ) ); 332 NSLog( QTStringFromTime( incTime ) ); 333 NSLog( QTStringFromTime( newtime ) ); 334 I get the following results: 335 336 0:00:00:00.00/48000 337 0:00:00:00.00/1000000 338 0:00:00:00.00/1000000 339 I have also tried setting the time string to @"0:00:02:00.00", @"0:0:2:0.0", and other variations. No luck. 340 341 What am I doing wrong? 342 343 A: You'll notice the following comment in QTTime.h: 344 345 // ,,,dd:hh:mm:ss.ff/ts 346 which translates into: 347 348 days:hours:minutes:seconds:frames/timescale 349 So you should try a string like: 350 351 QTTime incTime = QTTimeFromString( @"00:00:02:00.00/600" ); 352 NSLog( QTStringFromTime( incTime ) ); 353 */ 354 355 } 356 357 virtual void quit() 358 { 359 close(); 360 } 361 362 virtual void setTimeMultiplier(double the_rate) 363 { 364 NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init]; 365 [qtMovie setRate:the_rate]; 366 [autorelease_pool drain]; 367 } 368 virtual double getTimeMultiplier() const 369 { 370 NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init]; 371 float the_rate = [qtMovie rate]; 372 [autorelease_pool drain]; 373 return the_rate; 374 } 375 376 CVReturn handleCoreVideoCallback( 377 CVDisplayLinkRef display_link, 378 const CVTimeStamp* in_now, 379 const CVTimeStamp* in_output_time, 380 CVOptionFlags flags_in, 381 CVOptionFlags* flags_out, 382 void* user_data 383 ) 384 { 385// NSLog(@"In handleCoreVideoCallback"); 386 if(NULL == pixelBufferContextForQTOpenGL) 387 { 388// NSLog(@"pixelBufferContextForQTOpenGL is NULL"); 389 return kCVReturnSuccess; 390 } 391 // CoreVideo callbacks happen on a secondary thread. 392 // So we need a new autorelease pool for this thread. 393 NSAutoreleasePool* autorelease_pool = [[NSAutoreleasePool alloc] init]; 394 // check for new frame 395 396 397 /* 398 * Notes: The SHARE_CVPIXELBUFFER stuff reuses the same memory allocated by Core Video 399 * for the osg::Image data. This avoids extra memcpy's. 400 * FIXME: This probably needs locking. What is the locking model for osg::Image? 401 * Since Core Video operates on a high priority background thread, it is possible 402 * that the osg::Image could be utilized while we are updating with new frame data. 403 * Experimentally, I have not gotten any crashes, but I have noticed flickering 404 * in my original implementation where I would first set the osg::Image data to NULL 405 * before releasing the CVPixelBuffer. To avoid the flickering, I have now implemented 406 * a double-buffering technique where I immediately provide the new data and then 407 * clean up after the swap. 408 */ 409 if(QTVisualContextIsNewImageAvailable(pixelBufferContextForQTOpenGL, in_output_time)) 410 { 411#if SHARE_CVPIXELBUFFER 412 size_t previous_swap_frame_index = currentSwapFrameIndex; 413 414 // flip the active swap buffer 415 if(0 == currentSwapFrameIndex) 416 { 417 currentSwapFrameIndex = 1; 418 } 419 else 420 { 421 currentSwapFrameIndex = 0; 422 } 423 424 OSStatus error_status = QTVisualContextCopyImageForTime(pixelBufferContextForQTOpenGL, NULL, in_output_time, &swapFrame[currentSwapFrameIndex]); 425 // the above call may produce a null frame so check for this first 426 // if we have a frame, then draw it 427 if ((noErr == error_status) && (NULL != swapFrame[currentSwapFrameIndex])) 428 { 429 size_t buffer_width = CVPixelBufferGetWidth(swapFrame[currentSwapFrameIndex]); 430 size_t buffer_height = CVPixelBufferGetHeight(swapFrame[currentSwapFrameIndex]); 431// NSLog(@"CVPixelBuffer w=%d, h=%d", buffer_width, buffer_height); 432 // buffer_width = 480; 433 // buffer_height = 320; 434 435#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) 436 CVPixelBufferLockBaseAddress( swapFrame[currentSwapFrameIndex], kCVPixelBufferLock_ReadOnly ); 437#else 438 CVPixelBufferLockBaseAddress( swapFrame[currentSwapFrameIndex], 0 ); 439#endif 440 void* raw_pixel_data = CVPixelBufferGetBaseAddress(swapFrame[currentSwapFrameIndex]); 441 442 setImage(buffer_width,buffer_height,1, 443 GL_RGBA8, 444 GL_BGRA, 445 GL_UNSIGNED_INT_8_8_8_8_REV, 446 (unsigned char *)raw_pixel_data, 447 osg::Image::NO_DELETE, 448 1); 449 // seems to have no effect. Flip image the hard way 450 // setOrigin(osg::Image::TOP_LEFT); 451 // flipVertical(); 452 453 454 CVPixelBufferUnlockBaseAddress( swapFrame[currentSwapFrameIndex], 0 ); 455 } 456 457 // Now clean up previous frame 458 // Release the previous frame. (This is safe to call even if it is NULL.) 459 CVPixelBufferRelease(swapFrame[previous_swap_frame_index]); 460 swapFrame[previous_swap_frame_index] = NULL; 461 462#else 463 // if we have a previous frame release it 464 if (NULL != currentFrame) 465 { 466 CVPixelBufferRelease(currentFrame); 467 currentFrame = NULL; 468 } 469 470 // get a "frame" (image buffer) from the Visual Context, indexed by the provided time 471 OSStatus error_status = QTVisualContextCopyImageForTime(pixelBufferContextForQTOpenGL, NULL, in_output_time, ¤tFrame); 472 // the above call may produce a null frame so check for this first 473 // if we have a frame, then draw it 474 if ((noErr == error_status) && (NULL != currentFrame)) 475 { 476 size_t buffer_width = CVPixelBufferGetWidth(currentFrame); 477 size_t buffer_height = CVPixelBufferGetHeight(currentFrame); 478 // NSLog(@"CVPixelBuffer w=%d, h=%d", buffer_width, buffer_height); 479 // buffer_width = 480; 480 // buffer_height = 320; 481 482 CVPixelBufferLockBaseAddress( currentFrame, kCVPixelBufferLock_ReadOnly ); 483 484 void* raw_pixel_data = CVPixelBufferGetBaseAddress(currentFrame); 485 486 487 /* 488 NSLog(@"CVPixelBufferGetDataSize(currentFrame)=%d", CVPixelBufferGetDataSize(currentFrame)); 489 NSLog(@"CVPixelBufferIsPlanar(currentFrame)=%d", CVPixelBufferIsPlanar(currentFrame)); 490 NSLog(@"CVPixelBufferGetBytesPerRow(currentFrame)=%d", CVPixelBufferGetBytesPerRow(currentFrame)); 491 */ 492 493 // Don't understand why CVPixelBufferGetDataSize returns a slightly bigger size 494 // e.g. for a 480x320 movie, it is 32-bytes larger than 480*320*4 495 // memcpy(data(),raw_pixel_data,CVPixelBufferGetDataSize(currentFrame)); 496 memcpy(data(),raw_pixel_data,buffer_width*buffer_height*4); 497 498 // flipVertical(); 499 dirty(); 500 CVPixelBufferUnlockBaseAddress( currentFrame, 0 ); 501 } 502#endif 503 } // end QTVisualContextIsNewImageAvailable() 504 505 506 [autorelease_pool drain]; 507 return kCVReturnSuccess; 508 } 509 510 // TODO: OSG really needs some kind of notification callback for this so your OSG can find out that 511 // the movie size has changed. 512 void handleMovieNaturalSizeDidChange() 513 { 514// NSSize movie_size = [[qtMovie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]; 515// NSLog(@"handleMovieNaturalSizeDidChange=%f, %f", movie_size.width, movie_size.height); 516 pause(); 517 QTVisualContextRelease(pixelBufferContextForQTOpenGL); 518 pixelBufferContextForQTOpenGL = NULL; 519 play(); 520 } 521 522 // Untested: I think the important case to handle is usually live streaming and there is underrun. 523 void handleMovieLoadStateDidChange() 524 { 525// NSLog(@"handleMovieLoadStateDidChange"); 526 if( (PLAYING == _status) && ([qtMovie rate] == 0.0) ) // if should be playing, but not playing 527 { 528// NSLog(@"not playing"); 529 if([[qtMovie attributeForKey:QTMovieLoadStateAttribute] longValue] >= kMovieLoadStatePlaythroughOK) 530 { 531// NSLog(@"handleMovieLoadStateDidChangeCallback play"); 532 533 [qtMovie play]; 534 } 535 } 536 else 537 { 538// NSLog(@"playing"); 539 } 540 } 541 542 void handleMovieDidEnd() 543 { 544 pause(); 545 QTVisualContextRelease(pixelBufferContextForQTOpenGL); 546 pixelBufferContextForQTOpenGL = NULL; 547 // should I rewind? What is the expected behavior? 548 } 549 550 protected: 551 CVDisplayLinkRef displayLink; 552#if SHARE_CVPIXELBUFFER 553 CVPixelBufferRef swapFrame[2]; 554 size_t currentSwapFrameIndex; 555#else 556 CVPixelBufferRef currentFrame; 557#endif 558 QTMovie* qtMovie; 559 QTVisualContextRef pixelBufferContextForQTOpenGL; 560// id movieNotificationHandler; 561 MovieNotificationHandler* movieNotificationHandler; 562 563 virtual ~QTKitImageStream() 564 { 565 close(); 566 if (NULL != displayLink) 567 { 568 if(CVDisplayLinkIsRunning(displayLink)) 569 { 570 CVDisplayLinkStop(displayLink); 571 } 572 CVDisplayLinkRelease(displayLink); 573 displayLink = NULL; 574 } 575 CFRelease(movieNotificationHandler); 576 movieNotificationHandler = nil; 577 } 578 579 void close() 580 { 581 [[NSNotificationCenter defaultCenter] removeObserver:movieNotificationHandler 582 name:QTMovieLoadStateDidChangeNotification object:qtMovie]; 583 584 [[NSNotificationCenter defaultCenter] removeObserver:movieNotificationHandler 585#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) 586 name:QTMovieNaturalSizeDidChangeNotification 587#else 588 name:QTMovieSizeDidChangeNotification 589#endif 590 591 object:qtMovie]; 592 593 [[NSNotificationCenter defaultCenter] removeObserver:movieNotificationHandler 594 name:QTMovieDidEndNotification object:qtMovie]; 595 596 if(CVDisplayLinkIsRunning(displayLink)) 597 { 598 CVDisplayLinkStop(displayLink); 599 } 600 601 QTVisualContextRelease(pixelBufferContextForQTOpenGL); 602 pixelBufferContextForQTOpenGL = NULL; 603#if SHARE_CVPIXELBUFFER 604 CVPixelBufferRelease(swapFrame[0]); 605 swapFrame[0] = NULL; 606 CVPixelBufferRelease(swapFrame[1]); 607 swapFrame[1] = NULL; 608 currentSwapFrameIndex = 0; 609#else 610 CVPixelBufferRelease(currentFrame); 611 currentFrame = NULL; 612#endif 613 614 if(nil != qtMovie) 615 { 616 CFRelease(qtMovie); 617 qtMovie = nil; 618 } 619 } 620 virtual void applyLoopingMode() 621 { 622 if(NO_LOOPING == _loopingMode) 623 { 624 [qtMovie setAttribute:[NSNumber numberWithBool:NO] forKey:QTMovieLoopsAttribute]; 625 } 626 else 627 { 628 [qtMovie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieLoopsAttribute]; 629 } 630 } 631 632 633 634}; 635 636 637} 638 639static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef display_link, 640 const CVTimeStamp* in_now, 641 const CVTimeStamp* in_output_time, 642 CVOptionFlags flags_in, 643 CVOptionFlags* flags_out, 644 void* user_data) 645{ 646 if(NULL != user_data) 647 { 648 osgQTKit::QTKitImageStream* qtkit_image_stream = reinterpret_cast<osgQTKit::QTKitImageStream*>(user_data); 649 return qtkit_image_stream->handleCoreVideoCallback(display_link, in_now, in_output_time, flags_in, flags_out, NULL); 650 } 651 return kCVReturnSuccess; 652} 653 654class ReaderWriterQTKit : public osgDB::ReaderWriter 655{ 656 public: 657 658 ReaderWriterQTKit() 659 { 660 supportsExtension("mov","Quicktime movie format"); 661 supportsExtension("mpg","Mpeg movie format"); 662 supportsExtension("mp4","Mpeg movie format"); 663 supportsExtension("mpv","Mpeg movie format"); 664 supportsExtension("mpeg","Mpeg movie format"); 665 666 // only with Perian 667 supportsExtension("avi",""); 668 supportsExtension("xvid",""); 669 // only with Flip4Mac 670 supportsExtension("wmv",""); 671 672 } 673 virtual bool acceptsExtension(const std::string& extension) const 674 { 675 return 676 osgDB::equalCaseInsensitive(extension,"mov") || 677 osgDB::equalCaseInsensitive(extension,"mpg") || 678 osgDB::equalCaseInsensitive(extension,"mp4") || 679 osgDB::equalCaseInsensitive(extension,"mpv") || 680 osgDB::equalCaseInsensitive(extension,"mpeg") || 681 osgDB::equalCaseInsensitive(extension,"avi") || 682 osgDB::equalCaseInsensitive(extension,"xvid") || 683 osgDB::equalCaseInsensitive(extension,"wmv"); 684 685 } 686 687 virtual ~ReaderWriterQTKit() 688 { 689 OSG_INFO<<"~ReaderWriterQTKit()"<<std::endl; 690 } 691 692 virtual const char* className() const { return "QTKit ImageStream Reader"; } 693 694 virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) const 695 { 696 std::string ext = osgDB::getLowerCaseFileExtension(file); 697 if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED; 698 699 std::string fileName; 700 if (ext=="QTKit") 701 { 702 fileName = osgDB::findDataFile( osgDB::getNameLessExtension(file), options); 703 OSG_INFO<<"QTKit stipped filename = "<<fileName<<std::endl; 704 } 705 else 706 { 707 fileName = osgDB::findDataFile( file, options ); 708 if (fileName.empty()) return ReadResult::FILE_NOT_FOUND; 709 } 710 711 OSG_INFO<<"ReaderWriterQTKit::readImage "<< file<< std::endl; 712 713 osg::ref_ptr<osgQTKit::QTKitImageStream> imageStream = new osgQTKit::QTKitImageStream(); 714 715 if (!imageStream->open(fileName)) return ReadResult::FILE_NOT_HANDLED; 716 717 return imageStream.release(); 718 } 719 720 protected: 721 722 723}; 724 725 726@implementation MovieNotificationHandler 727 728- (void) setImageStream:(osgQTKit::QTKitImageStream*)image_stream 729{ 730 imageStream = image_stream; 731} 732 733// I need to verify if this is being called back on a non-main thread. 734// My initial observation led me to think it was being called on a background thread, 735// but it could be that I was just confused which thread was the main thread since 736// CoreVideo was doing a bunch of stuff on its own background thread. 737- (void) movieNaturalSizeDidChange:(NSNotification*)the_notification 738{ 739 imageStream->handleMovieNaturalSizeDidChange(); 740} 741 742- (void) movieLoadStateDidChange:(NSNotification*)the_notification 743{ 744 imageStream->handleMovieLoadStateDidChange(); 745} 746 747- (void) movieDidEnd:(NSNotification*)the_notification 748{ 749 imageStream->handleMovieDidEnd(); 750} 751@end 752 753 754// now register with Registry to instantiate the above 755// reader/writer. 756REGISTER_OSGPLUGIN(QTKit, ReaderWriterQTKit) 757