To complete the project nib file, you’ll need to define the instance variables that point to the capture session, as well as to the device input and decompressed video output objects.
In your Xcode project, you need to add the instance variables to the interface declaration. Add these lines of code in your MyDocument.h declaration file:
@interface MyDocument : NSDocument |
{ |
QTMovie *mMovie |
QTCaptureSession *mCaptureSession; |
QTCaptureDeviceInput *mCaptureDeviceInput; |
QTCaptureDecompressedVideoOutput *mCaptureDecompressedVideoOutput; |
} |
The mMovie instance variable points to the QTMovie object while the mCaptureSession instance variable points to the QTCaptureSession object. Likewise, the *mCaptureDeviceInput instance variable points to the QTCaptureDeviceInput object while the next line declares that the mCaptureDecompressedVideoOutput instance variable points to the QTCaptureDecompressedVideoOutput object.
There is one more instance variable you need to declare in this file: mCurrentImageBuffer. This instance variable stores the most recent frame that you’ve grabbed in a CVImageBufferRef. Add this line of code, following your last declaration:
CVImageBufferRef mCurrentImageBuffer; |
That completes the code you need to add to your MyDocument.h file. Now you want to open your MyDocument.m file and prepare to add the following blocks of code to your project. Note that the code is commented for better understanding and comprehension.
Important: There is a specific, though not necessarily rigid, order of steps you want to follow in constructing your code. Think of these as specific tasks you want to accomplish in your project.
Create an empty movie that writes to mutable data in memory, using the initToWritableData: method.
Set up a capture session that outputs the raw frames you want to grab.
Find a video device and add a device input for that device to the capture session.
Add a decompressed video output that returns the raw frames you’ve grabbed to the session and then previews the video from the session in the document window.
Start the session, using the startRunning method you’ve used previously in the MyRecorder sample code.
Call a delegate method whenever the QTCaptureDecompressedVideoOutput object receives a frame.
Store the latest frame. Do this in a @synchronized block because the delegate method is not called on the main thread.
Get the most recent frame. Do this in a @synchronized block because the delegate method that sets the most recent frame is not called on the main thread.
Create an NSImage and add it to the movie.
Following the steps outlined above, add this block of code to your MyDocument.m file.
- (void)windowControllerDidLoadNib:(NSWindowController *) aController |
{ |
NSError *error = nil; |
[super windowControllerDidLoadNib:aController]; |
[[aController window] setDelegate:self]; |
if (!mMovie) { |
// Create an empty movie that writes to mutable data in memory |
mMovie = [[QTMovie alloc] initToWritableData:[NSMutableData data] error:&error]; |
if (!mMovie) { |
[[NSAlert alertWithError:error] runModal]; |
return; |
} |
} |
[mMovieView setMovie:mMovie]; |
if (!mCaptureSession) { |
// Set up a capture session that outputs raw frames |
BOOL success; |
mCaptureSession = [[QTCaptureSession alloc] init]; |
// Find a video device |
QTCaptureDevice *device = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeVideo]; |
success = [device open:&error]; |
if (!success) { |
[[NSAlert alertWithError:error] runModal]; |
return; |
} |
// Add a device input for that device to the capture session |
mCaptureDeviceInput = [[QTCaptureDeviceInput alloc] initWithDevice:device]; |
success = [mCaptureSession addInput:mCaptureDeviceInput error:&error]; |
if (!success) { |
[[NSAlert alertWithError:error] runModal]; |
return; |
} |
// Add a decompressed video output that returns raw frames to the session |
mCaptureDecompressedVideoOutput = [[QTCaptureDecompressedVideoOutput alloc] init]; |
[mCaptureDecompressedVideoOutput setDelegate:self]; |
success = [mCaptureSession addOutput:mCaptureDecompressedVideoOutput error:&error]; |
if (!success) { |
[[NSAlert alertWithError:error] runModal]; |
return; |
} |
// Preview the video from the session in the document window |
[mCaptureView setCaptureSession:mCaptureSession]; |
// Start the session |
[mCaptureSession startRunning]; |
} |
} |
Add these lines to handle window closing notifications for your device input and stop the capture session:
- (void)windowWillClose:(NSNotification *)notification |
{ |
[mCaptureSession stopRunning]; |
QTCaptureDevice *device = [mCaptureDeviceInput device]; |
if ([device isOpen]) |
[device close]; |
} |
Insert the following block of code to handle deallocation of memory for your capture objects:
- (void)dealloc |
{ |
[mMovie release]; |
[mCaptureSession release]; |
[mCaptureDeviceInput release]; |
[mCaptureDecompressedVideoOutput release]; |
[super dealloc]; |
} |
Add the following lines of code to specify the output destination for your recorded media, in this case an editable QuickTime movie:
- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError |
{ |
QTMovie *newMovie = [[QTMovie alloc] initWithURL:absoluteURL error:outError]; |
if (newMovie) { |
[newMovie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieEditableAttribute]; |
[mMovie release]; |
mMovie = newMovie; |
} |
return (newMovie != nil); |
} |
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError |
{ |
return [mMovie writeToFile:[absoluteURL path] withAttributes:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:QTMovieFlatten] error:outError]; |
} |
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError |
{ |
return [mMovie writeToFile:[absoluteURL path] withAttributes:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:QTMovieFlatten] error:outError]; |
} |
Add these lines of code to call a delegate method whenever the QTCaptureDecompressedVideoOutput object receives a frame:
- (void)captureOutput:(QTCaptureOutput *)captureOutput didOutputVideoFrame:(CVImageBufferRef)videoFrame withSampleBuffer:(QTSampleBuffer *)sampleBuffer fromConnection:(QTCaptureConnection *)connection |
{ |
// Store the latest frame |
// This must be done in a @synchronized block because this delegate method is not called on the main thread |
CVImageBufferRef imageBufferToRelease; |
CVBufferRetain(videoFrame); |
@synchronized (self) { |
imageBufferToRelease = mCurrentImageBuffer; |
mCurrentImageBuffer = videoFrame; |
} |
CVBufferRelease(imageBufferToRelease); |
} |
Now you want to specify the addFrame: action method and get the most recent frame that you’ve grabbed. Do this in a @synchronized block because the delegate method that sets the most recent frame is not called on the main thread. Note that you’re wrapping a CVImageBufferRef object into an NSImage. After you create an NSImage, you can then add it to the movie.
- (IBAction)addFrame:(id)sender |
{ |
CVImageBufferRef imageBuffer; |
@synchronized (self) { |
imageBuffer = CVBufferRetain(mCurrentImageBuffer); |
} |
if (imageBuffer) { |
// Create an NSImage and add it to the movie |
NSCIImageRep *imageRep = [NSCIImageRep imageRepWithCIImage:[CIImage imageWithCVImageBuffer:imageBuffer]]; |
NSImage *image = [[[NSImage alloc] initWithSize:[imageRep size]] autorelease]; |
[image addRepresentation:imageRep]; |
CVBufferRelease(imageBuffer); |
[mMovie addImage:image forDuration:QTMakeTime(1, 10) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: |
@"jpeg", QTAddImageCodecType, nil]]; |
[mMovie setCurrentTime:[mMovie duration]]; |
[mMovieView setNeedsDisplay:YES]; |
[self updateChangeCount:NSChangeDone]; |
} |
} |
Last updated: 2007-10-31