iPhone SDK 4 AVFoundation - 如何正确使用captureStillImageAsynchronouslyFromConnection方法?

22

我正在尝试使用iPhone上的新AVFoundation框架来拍摄静态照片。

通过按钮按下调用此方法。我可以听到快门声,但看不到日志输出。如果我多次调用此方法,相机预览将会冻结。

是否有任何教程可以介绍如何使用captureStillImageAsynchronouslyFromConnection

[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:
                [[self stillImageOutput].connections objectAtIndex:0]
                     completionHandler:^(CMSampleBufferRef imageDataSampleBuffer,
                            NSError *error) {
                                              NSLog(@"inside");
                            }];
- (void)initCapture {
    AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput 
                                          deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] 
                                          error:nil];

    AVCaptureVideoDataOutput *captureOutput = [[AVCaptureVideoDataOutput alloc] init];

    captureOutput.alwaysDiscardsLateVideoFrames = YES; 

    dispatch_queue_t queue;
    queue = dispatch_queue_create("cameraQueue", NULL);
    [captureOutput setSampleBufferDelegate:self queue:queue];
    dispatch_release(queue);

    NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey; 
    NSNumber* value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA]; 
    NSDictionary* videoSettings = [NSDictionary dictionaryWithObject:value forKey:key]; 
    [captureOutput setVideoSettings:videoSettings]; 

    self.captureSession = [[AVCaptureSession alloc] init];
    self.captureSession.sessionPreset = AVCaptureSessionPresetLow;

    [self.captureSession addInput:captureInput];
    [self.captureSession addOutput:captureOutput];

    self.prevLayer = [AVCaptureVideoPreviewLayer layerWithSession: self.captureSession];

    [self.prevLayer setOrientation:AVCaptureVideoOrientationLandscapeLeft];

    self.prevLayer.frame = CGRectMake(0.0, 0.0, 480.0, 320.0);
    self.prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    [self.view.layer addSublayer: self.prevLayer];


    // 设置默认文件输出
    AVCaptureStillImageOutput *_stillImageOutput = [[[AVCaptureStillImageOutput alloc] init] autorelease];
    NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
                                    AVVideoCodecJPEG, AVVideoCodecKey,
                                    nil];
    [_stillImageOutput setOutputSettings:outputSettings];
    [outputSettings release];
    [self setStillImageOutput:_stillImageOutput];   

    if ([self.captureSession canAddOutput:stillImageOutput]) {
        [self.captureSession addOutput:stillImageOutput];
    }

    [self.captureSession commitConfiguration];
    [self.captureSession startRunning];

}

我不知道这在2010年是否正确,但截至2011年末,我已经可以轻松地同时使用captureStillImageAsynchronouslyFromConnection和AVCaptureVideoDataOutput的代理captureOutput以及使用previewLayer获取视频源。 我从stillImage获得了一个5兆像素的图像,852x640用于视频源和previewLayer。 - mahboudz
以上评论是基于iPhone4的。在iPhone 4s上,我可以获得8兆像素的静态图像,同时获取852x640的视频和预览层。 - mahboudz
initWithSession和layerWithSession有什么区别?文档没有讨论何时使用其中之一。http://developer.apple.com/library/ios/#documentation/AVFoundation/Reference/AVCaptureVideoPreviewLayer_Class/Reference/Reference.html - knite
5个回答

62

经过多次尝试和错误,我成功地找到了如何做到这一点。

提示:苹果官方文档是错误的。他们提供的代码实际上无法正常工作。

我在这里写了详细的步骤说明:

http://red-glasses.com/index.php/tutorials/ios4-take-photos-with-live-video-preview-using-avfoundation/

链接中有很多代码,但总的来说:

-(void) viewDidAppear:(BOOL)animated
{
    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    session.sessionPreset = AVCaptureSessionPresetMedium;

    CALayer *viewLayer = self.vImagePreview.layer;
    NSLog(@"viewLayer = %@", viewLayer);

    AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];

    captureVideoPreviewLayer.frame = self.vImagePreview.bounds;
    [self.vImagePreview.layer addSublayer:captureVideoPreviewLayer];

    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    NSError *error = nil;
    AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
    if (!input) {
        // Handle the error appropriately.
        NSLog(@"ERROR: trying to open camera: %@", error);
    }
    [session addInput:input];

stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys: AVVideoCodecJPEG, AVVideoCodecKey, nil];
[stillImageOutput setOutputSettings:outputSettings];

[session addOutput:stillImageOutput];

    [session startRunning];
}

-(IBAction) captureNow
{
    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; }
    }

    NSLog(@"about to request a capture from: %@", stillImageOutput);
    [stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
    {
         CFDictionaryRef exifAttachments = CMGetAttachment( imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
         if (exifAttachments)
         {
            // Do something with the attachments.
            NSLog(@"attachements: %@", exifAttachments);
         }
        else
            NSLog(@"no attachments");

        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
        UIImage *image = [[UIImage alloc] initWithData:imageData];

        self.vImage.image = image;
     }];
}

4
我非常赞同这个观点。谢谢你分享! - Art Gillespie
Tscott(在下面的评论中)有Adam代码的工作示例。我从Github下载并安装了它,最初在我的iPad上无法运行。然而,在检查xib文件后,我发现他将iPhone xib文件输出连接到了代码,但是iPad xib为空白的。在将imageview、标签、按钮和其他对象添加到iPad xib文件后,我成功地运行了代码。 - Miriam P. Raphael
WordPress太烂了 :( - 升级WordPress会导致网站崩溃,而且不报任何错误。现在已经修复了。 - Adam
1
我知道我来晚了,但是你用来获取视频连接的那两个 for 循环可以被 [stillImageOutput connectionWithMediaType:AVMediaTypeVideo] 替换(我不是专家,但至少对我有效...)。无论如何,这个例子真是救命稻草!感谢您。 - Alex
我非常困惑,为什么我的 AVCaptureStillIMageOutput 没有连接。原来是因为我在 viewWillAppear 中初始化了 AVCaptureSession,而它应该在 viewDidAppear 中初始化。我真傻。这让我浪费了一整天的时间。感谢你的答案! - dvdchr
显示剩余2条评论

16

当4.0版本还在测试阶段时,我们遇到了这个问题。我尝试了很多方法,下面是具体操作:

  • AVCaptureStillImageOutput和AVCaptureVideoDataOutput似乎不能良好地协同工作。如果视频输出正在运行,则图像输出似乎永远无法完成(直到您通过将手机置于睡眠状态来暂停会话;然后似乎会输出一张图像)。
  • AVCaptureStillImageOutput只能在AVCaptureSessionPresetPhoto模式下正常工作;否则您将获得经过JPEG编码的视频帧。可能最好使用更高质量的BGRA帧(顺便说一下,相机的原始输出似乎是BGRA格式;它似乎没有2vuy/420v的颜色子采样)。
  • 视频(除"照片"外的所有内容)和照片模式似乎根本不同。如果会话处于照片模式,则您永远不会得到任何视频帧(也不会出现错误)。也许他们已经改变了这一点...
  • 似乎无法拥有两个捕获会话(一个具有视频预设以及视频输出,另一个具有照片预设和图像输出)。他们可能已经修复了这个问题。
  • 您可以停止会话,将预设更改为照片模式,启动会话,拍照片,并在照片完成后停止,将预设改回,并再次启动。这需要一段时间,视频预览层会停滞并且看起来很糟糕(它会重新调整曝光水平)。这在测试版中有时会死锁(在调用-stopRunning之后,session.running仍然为YES)。
  • 您可能可以禁用AVCaptureConnection(它应该能够工作)。我记得这会导致死锁;他们可能已经修复了这个问题。

最终我只是捕获了视频帧。"拍照"按钮只是设置一个标志;在视频帧回调中,如果设置了该标志,则返回视频帧而不是UIImage*。这对于我们的图像处理需求足够了— "拍照"存在主要是为了让用户得到负面反馈(以及提交bug报告的选项);我们实际上不需要2/3/5百万像素的图像,因为它们需要很长时间来处理。

如果视频帧不够好(即您想在高分辨率图像捕获之间捕获取景器帧),我首先会查看是否已经修复了使用多个AVCapture会话的问题,因为那是唯一可以设置两个预设的方式。
可能值得提交一个bug。我在4.0 GM发布时提交了一个bug,Apple要求我提供一些示例代码,但那时我已决定使用视频帧解决方法,并进行了发布。
此外,“低”预设非常低分辨率(并导致低分辨率、低帧率的视频预览)。如果可用,我会选择640x480,否则回退到“中等”。

1
AVCaptureStillImageOutput 只能与 AVCaptureSessionPresetPhoto 合理地配合使用,这是完全正确的。 - Paresh Navadiya

6
这对我来说是非常有帮助的——我曾经陷入了使用AVCam示例的泥潭中很长一段时间。
这是一个完整的工作项目,其中包含我的注释,解释了正在发生的事情。它展示了如何使用捕获管理器与多个输出。在此示例中有两个输出。
第一个是上面示例的静态图像输出。
第二个提供逐帧访问从相机输出的视频。如果您愿意,可以添加更多代码以执行有趣的操作。在此示例中,我只是在委托回调中更新屏幕上的帧计数器。 https://github.com/tdsltm/iphoneStubs/tree/master/VideoCamCaptureExample-RedGlassesBlog/VideoCamCaptureExample-RedGlassesBlog

工作得很好。提醒一下从Github下载此代码的任何人 - iPad xib文件是空白的。您需要复制iPhone xib对象并将它们粘贴到iPad xib文件中。然后将对象连接到它们各自的插座。 - Miriam P. Raphael
对我不起作用。在iPhone 5S上的beta8版本和iOS 7.1.2的iPad上都不行。 - Tõnu Samuel

3

0

你应该使用 Adam 的答案,但如果你使用 Swift(像现在大多数人一样),这里是他代码的 Swift 1.2 版本:

  1. 确保你 import ImageIO
  2. 添加一个属性 private var stillImageOutput: AVCaptureStillImageOutput!
  3. captureSession.startRunning() 之前实例化 stillImageOutput

像这样:

stillImageOutput = AVCaptureStillImageOutput()
stillImageOutput.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
captureSession.addOutput(stillImageOutput)

然后使用以下代码来捕获图像:

private func captureImage() {
    var videoConnection: AVCaptureConnection?
    for connection in stillImageOutput.connections as! [AVCaptureConnection] {
        for port in connection.inputPorts {
            if port.mediaType == AVMediaTypeVideo {
                videoConnection = connection
                break
            }
        }
        if videoConnection != nil {
            break
        }
    }
    print("about to request a capture from: \(stillImageOutput)")
    stillImageOutput.captureStillImageAsynchronouslyFromConnection(videoConnection) { (imageSampleBuffer: CMSampleBuffer!, error: NSError!) -> Void in
        let exifAttachments = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, nil)
        if let attachments = exifAttachments {
            // Do something with the attachments
            print("attachments: \(attachments)")
        } else {
            print("no attachments")
        }
        let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageSampleBuffer)
        let image = UIImage(data: imageData)
        // Do something with the image
    }
}

这一切都假定您已经设置好了一个AVCaptureSession,只需要从中获取静态图像,就像我一样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接