user1904273
user1904273

Reputation: 4764

DIsplaying Asynchronous stillImageOutput from AVVideoCapture Session using AVFoundation

I am capturing video in preview mode and would like to display a still image captured by the camera.

I currently save the image and capture output to ivars defined in the interface as:

UIImage *snapshot
AVCaptureStillImageOutput* stillImageOutput;

The video displays fine. However, when I try to capture and display a still image, nothing is appearing and, in fact, the debugger shows the stillImageOutput and image are nil. I think this may be a timing issue with the asynchronous capture and that I need to use a completion handler, but I am weak on completion handlers.

What is the proper way to display a still image immediately after capturing it without tying up UI:

Code to capture still:

- (void)takeSnapshot {
 AVCaptureConnection *videoConnection = nil;
 for (AVCaptureConnection *connection in stillImageOutput.connections) {
 for (AVCaptureInputPort *port in [connection inputPorts]) {
 if ([[port mediaType] isEqual:AVMediaTypeVideo]) {
 videoConnection = connection;
 break;
 }
 }
 if (videoConnection) {
 break;
 }
 }
 [stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection
 completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
 if (imageDataSampleBuffer != NULL) {
 NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
 snapshot = [UIImage imageWithData:imageData];
 }
 }];
 }

Code to display still. Note absence of completion handler which may be issue, however, I'm not sure how to write that...

 [self takeSnapshot];
 self.imageView.image = snapshot;

Upvotes: 0

Views: 101

Answers (1)

R4N
R4N

Reputation: 2595

I would change the takeSnapshot method to take in a completion block and then call that completion block within the completion block of your other async method: captureStillImageAsynchronouslyFromConnection:completionHandler

Here's an example of a method taking a completion block and then calling back to it in the completion block of a method called internally:

// this correlates to your takeSnapshot method
// you want to add a completion portion to this method
- (void)doSomethingAsynchronouslyWithCompletion:(void (^)(NSData *completionData))completion {
    // call your other async method
    [self anotherAsyncMethodWithItsOwnCompletion:^(NSData *completionDataFromSecondMethod) {
        if (completionDataFromSecondMethod.length > 0) {
            // this is where you would receive the CMSampleBufferRef from the completion handler of captureStillImageAsynchronouslyFromConnection:completionHandler
            // and convert it over to to data
            // make sure the completion block isn't nil if it's nullable
            if (completion) {
                // you would want to pass back the NSData imageData in the completion block here
                completion(completionDataFromSecondMethod);
            }
        }
    }];
}

// this method would simulate the captureStillImageAsynchronouslyFromConnection:completionHandler: method
- (void)anotherAsyncMethodWithItsOwnCompletion:(void (^)(NSData * completionDataFromSecondMethod))anotherCompletion {
    // this is just to simulate some time waiting for the asnyc task to complete
    // never call sleep in your own code
    sleep(3);
    if (anotherCompletion) {
        // this simulates the fake CFSampleBufferRef passed back by the captureStillImage...
        NSData *fakeCompletionData = [@"FakeCompletionString" dataUsingEncoding:NSUTF8StringEncoding];
        anotherCompletion(fakeCompletionData);
    }
}

And an example of how you would call it:

    [self doSomethingAsynchronouslyWithCompletion:^(NSData *completionData) {
        if (completionData.length > 0) {
            // come back on the main queue to modify any UI Elements
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                // this is where you want want to set your self.imageView.image
                // self.imageView.image = [UIImage imageWithData:{{dataFromCompletion}}]
                NSLog(@"The completionString result = %@", [[NSString alloc] initWithData:completionData encoding:NSUTF8StringEncoding]);
            }];
        }
    }];

This link may be helpful for getting you started with block syntax: http://goshdarnblocksyntax.com

Upvotes: 1

Related Questions