// // CSGCamera.m // MotionTracker // // Created by Tim Omernick on 3/7/05. // Copyright 2005 Tim Omernick. All rights reserved. // // Portions of this file were inspired by Apple Computer, Inc.'s Cocoa SGDataProc example, which can be found here: // // Also, I'd like to thank Chris Meyer for his excellent -imageFromGWorld: method, which he gave me permission to use for this framework. #import "CSGCamera.h" #import "CSGImage.h" @interface CSGCamera (Private) - (void)_sequenceGrabberIdle; - (BOOL)_setupDecompression; - (void)_didUpdate; - (CSGImage *)_imageFromGWorld:(GWorldPtr)gworld; @end @interface CSGCamera (SequenceGrabber) pascal OSErr CSGCameraSGDataProc(SGChannel channel, Ptr data, long dataLength, long *offset, long channelRefCon, TimeValue time, short writeType, long refCon); @end @implementation CSGCamera // Init and dealloc - (void)dealloc; { [self stop]; [delegate release]; [super dealloc]; } // API - (void)setDelegate:(id)newDelegate; { if (delegate == newDelegate) return; [delegate release]; delegate = [newDelegate retain]; } - (BOOL)startWithSize:(NSSize)frameSize; { OSErr theErr; timeScale = 0; lastTime = 0; // Initialize movie toolbox theErr = EnterMovies(); if (theErr != noErr) { NSLog(@"EnterMovies() returned %ld", theErr); return NO; } // Open default sequence grabber component component = OpenDefaultComponent(SeqGrabComponentType, 0); if (!component) { NSLog(@"Could not open sequence grabber component."); return NO; } // Initialize sequence grabber component theErr = SGInitialize(component); if (theErr != noErr) { NSLog(@"SGInitialize() returned %ld", theErr); return NO; } // Don't make movie theErr = SGSetDataRef(component, 0, 0, seqGrabDontMakeMovie); if (theErr != noErr) { NSLog(@"SGSetDataRef() returned %ld", theErr); return NO; } // Create sequence grabber video channel theErr = SGNewChannel(component, VideoMediaType, &channel); if (theErr != noErr) { NSLog(@"SGNewChannel() returned %ld", theErr); return NO; } // Set the grabber's bounds boundsRect.top = 0; boundsRect.left = 0; boundsRect.bottom = frameSize.height; boundsRect.right = frameSize.width; // NSLog(@"boundsRect=(%d, %d, %d, %d)", boundsRect.top, boundsRect.left, boundsRect.bottom, boundsRect.right); theErr = SGSetChannelBounds(component, &boundsRect); // Create the GWorld theErr = QTNewGWorld(&gWorld, k32ARGBPixelFormat, &boundsRect, 0, NULL, 0); if (theErr != noErr) { NSLog(@"QTNewGWorld() returned %ld", theErr); return NO; } // Lock the pixmap if (!LockPixels(GetPortPixMap(gWorld))) { NSLog(@"Could not lock pixels."); return NO; } // Set GWorld theErr = SGSetGWorld(component, gWorld, GetMainDevice()); if (theErr != noErr) { NSLog(@"SGSetGWorld() returned %ld", theErr); return NO; } // Set the channel's bounds theErr = SGSetChannelBounds(channel, &boundsRect); if (theErr != noErr) { NSLog(@"SGSetChannelBounds(2) returned %ld", theErr); return NO; } // Set the channel usage to record theErr = SGSetChannelUsage(channel, seqGrabRecord); if (theErr != noErr) { NSLog(@"SGSetChannelUsage() returned %ld", theErr); return NO; } // Set data proc theErr = SGSetDataProc(component, NewSGDataUPP(&CSGCameraSGDataProc), (long)self); if (theErr != noErr) { NSLog(@"SGSetDataProc() returned %ld", theErr); return NO; } // Prepare theErr = SGPrepare(component, false, true); if (theErr != noErr) { NSLog(@"SGPrepare() returned %ld", theErr); return NO; } // Start recording theErr = SGStartRecord(component); if (theErr != noErr) { NSLog(@"SGStartRecord() returned %ld", theErr); return NO; } startTime = [NSDate timeIntervalSinceReferenceDate]; // Set up decompression sequence (camera -> GWorld) [self _setupDecompression]; // Start frame timer frameTimer = [[NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(_sequenceGrabberIdle) userInfo:nil repeats:YES] retain]; [self retain]; // Matches autorelease in -stop return YES; } - (BOOL)stop; { // Stop frame timer if (frameTimer) { [frameTimer invalidate]; [frameTimer release]; frameTimer = nil; } // Stop recording if (component) SGStop(component); ComponentResult theErr; // End decompression sequence if (decompressionSequence) { theErr = CDSequenceEnd(decompressionSequence); if (theErr != noErr) { NSLog(@"CDSequenceEnd() returned %ld", theErr); } decompressionSequence = 0; } // Close sequence grabber component if (component) { theErr = CloseComponent(component); if (theErr != noErr) { NSLog(@"CloseComponent() returned %ld", theErr); } component = NULL; } // Dispose of GWorld if (gWorld) { DisposeGWorld(gWorld); gWorld = NULL; } [self autorelease]; // Matches retain in -start return YES; } @end @implementation CSGCamera (Private) - (void)_sequenceGrabberIdle; { OSErr theErr; theErr = SGIdle(component); if (theErr != noErr) { NSLog(@"SGIdle returned %ld", theErr); return; } } - (BOOL)_setupDecompression; { ComponentResult theErr; ImageDescriptionHandle imageDesc = (ImageDescriptionHandle)NewHandle(0); theErr = SGGetChannelSampleDescription(channel, (Handle)imageDesc); if (theErr != noErr) { NSLog(@"SGGetChannelSampleDescription() returned %ld", theErr); return NO; } Rect sourceRect; sourceRect.top = 0; sourceRect.left = 0; sourceRect.right = (**imageDesc).width; sourceRect.bottom = (**imageDesc).height; MatrixRecord scaleMatrix; RectMatrix(&scaleMatrix, &sourceRect, &boundsRect); theErr = DecompressSequenceBegin(&decompressionSequence, imageDesc, gWorld, NULL, NULL, &scaleMatrix, srcCopy, NULL, 0, codecNormalQuality, bestSpeedCodec); if (theErr != noErr) { NSLog(@"DecompressionSequenceBegin() returned %ld", theErr); return NO; } DisposeHandle((Handle)imageDesc); return YES; } - (void)_didUpdate; { if ([delegate respondsToSelector:@selector(camera:didReceiveFrame:)]) { CSGImage *frameImage = [self _imageFromGWorld:gWorld]; if (frameImage) { [frameImage setSampleTime:startTime + ((double)lastTime / (double)timeScale)]; [delegate camera:self didReceiveFrame:frameImage]; } } } // Thanks to Chris Meyer from http://www.cocoadev.com/ - (CSGImage *)_imageFromGWorld:(GWorldPtr)gworld; { NSParameterAssert( gworld != NULL ); PixMapHandle pixMapHandle = GetGWorldPixMap( gworld ); if ( LockPixels( pixMapHandle ) ) { Rect portRect; GetPortBounds( gworld, &portRect ); int pixels_wide = (portRect.right - portRect.left); int pixels_high = (portRect.bottom - portRect.top); int bps = 8; int spp = 4; BOOL has_alpha = YES; NSBitmapImageRep *frameBitmap = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:pixels_wide pixelsHigh:pixels_high bitsPerSample:bps samplesPerPixel:spp hasAlpha:has_alpha isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:0 bitsPerPixel:0] autorelease]; CGColorSpaceRef dst_colorspaceref = CGColorSpaceCreateDeviceRGB(); CGImageAlphaInfo dst_alphainfo = has_alpha ? kCGImageAlphaPremultipliedLast : kCGImageAlphaNone; CGContextRef dst_contextref = CGBitmapContextCreate( [frameBitmap bitmapData], pixels_wide, pixels_high, bps, [frameBitmap bytesPerRow], dst_colorspaceref, dst_alphainfo ); void *pixBaseAddr = GetPixBaseAddr(pixMapHandle); long pixmapRowBytes = GetPixRowBytes(pixMapHandle); CGDataProviderRef dataproviderref = CGDataProviderCreateWithData( NULL, pixBaseAddr, pixmapRowBytes * pixels_high, NULL ); int src_bps = 8; int src_spp = 4; BOOL src_has_alpha = YES; CGColorSpaceRef src_colorspaceref = CGColorSpaceCreateDeviceRGB(); CGImageAlphaInfo src_alphainfo = src_has_alpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNone; CGImageRef src_imageref = CGImageCreate( pixels_wide, pixels_high, src_bps, src_bps * src_spp, pixmapRowBytes, src_colorspaceref, src_alphainfo, dataproviderref, NULL, NO, // shouldInterpolate kCGRenderingIntentDefault ); CGRect rect = CGRectMake( 0, 0, pixels_wide, pixels_high ); CGContextDrawImage( dst_contextref, rect, src_imageref ); CGImageRelease( src_imageref ); CGColorSpaceRelease( src_colorspaceref ); CGDataProviderRelease( dataproviderref ); CGContextRelease( dst_contextref ); CGColorSpaceRelease( dst_colorspaceref ); UnlockPixels( pixMapHandle ); CSGImage *image = [[CSGImage alloc] initWithSize:NSMakeSize(pixels_wide, pixels_high)]; [image addRepresentation:frameBitmap]; return [image autorelease]; } return NULL; } @end @implementation CSGCamera (SequenceGrabber) pascal OSErr CSGCameraSGDataProc(SGChannel channel, Ptr data, long dataLength, long *offset, long channelRefCon, TimeValue time, short writeType, long refCon) { CSGCamera *camera = (CSGCamera *)refCon; ComponentResult theErr; if (camera->timeScale == 0) { theErr = SGGetChannelTimeScale(camera->channel, &camera->timeScale); if (theErr != noErr) { NSLog(@"SGGetChannelTimeScale() returned %ld", theErr); return theErr; } } if (camera->gWorld) { CodecFlags ignore; theErr = DecompressSequenceFrameS(camera->decompressionSequence, data, dataLength, 0, &ignore, NULL); if (theErr != noErr) { NSLog(@"DecompressSequenceFrameS() returned %ld", theErr); return theErr; } } camera->lastTime = time; [camera _didUpdate]; return noErr; } @end