相机AVCam: Building a Camera App

Configure a Capture Session

AVCaptureSession接受来自摄像头和麦克风等捕捉设备的输入数据。接收到输入后,AVCaptureSession将该数据封送到适当的输出进行处理,最终生成电影文件或静态照片。配置捕获会话的输入和输出后,您会告诉它开始捕获,然后停止捕获。

private let session = AVCaptureSession()

AVCam默认选择后摄像头,并将摄像头捕获会话配置为将内容流式传输到视频预览视图。PreviewView是由AVCaptureVideoPreviewLayer支持的自定义UIView子类。AVFoundation没有PreviewView类,但示例代码创建了一个类以方便会话管理。
下图显示了会话如何管理输入设备和捕获输出:
在这里插入图片描述
将与AVCaptureSession的任何交互(包括其输入和输出)委托给专用串行调度队列(sessionQueue),以便交互不会阻塞主队列。执行任何涉及更改会话拓扑或中断其在单独调度队列上运行的视频流的配置,因为在队列处理更改之前,会话配置始终会阻止其他任务的执行。类似地,示例代码会分派其他任务,例如恢复中断的会话、切换捕获模式、切换相机以及将媒体写入会话队列中的文件,以便它们的处理不会阻止或延迟用户与应用程序的交互。
相反,代码将影响UI的任务(例如更新预览视图)分派到主队列,因为CALayer的子类AVCaptureVideoPreviewLayer是示例预览视图的支持层。您必须在主线程上操作UIView子类,以使它们及时、交互式地显示出来。
在viewDidLoad中,AVCam创建会话并将其分配给预览视图:

previewView.session = session

请求访问输入设备的授权

配置会话后,它就可以接受输入了。无论是相机还是麦克风,每个AVCaptureDevice都需要用户授权访问。AVFoundation使用AVAuthorizationStatus枚举授权状态,该状态通知应用程序用户是否限制或拒绝访问捕获设备。
有关为自定义授权请求准备应用程序的Info.plist的更多信息,请参阅在iOS上请求媒体捕获授权。

在后向和前向摄像头之间切换

changeCamera方法在用户点击UI中的按钮时处理相机之间的切换。它使用发现会话,该会话按首选项的顺序列出可用的设备类型,并接受其设备阵列中的第一个设备。例如,AVCam中的videoDeviceDiscoverySession查询应用程序运行的设备是否有可用的输入设备。此外,如果用户的设备有损坏的摄像头,那么它在设备阵列中不可用。

switch currentPosition {
   
case .unspecified, .front:
    newVideoDevice = backVideoDeviceDiscoverySession.devices.first
    
case .back:
    newVideoDevice = frontVideoDeviceDiscoverySession.devices.first
    
@unknown default:
    print("Unknown capture position. Defaulting to back, dual-camera.")
    newVideoDevice = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back)
}

如果发现会话在正确的位置找到相机,它将从捕获会话中删除以前的输入,并添加新相机作为输入。

// Remove the existing device input first, because AVCaptureSession doesn't support
// simultaneous use of the rear and front cameras.
self.session.removeInput(self.videoDeviceInput)

if self.session.canAddInput(videoDeviceInput) {
   
    NotificationCenter.default.removeObserver(self, name: .AVCaptureDeviceSubjectAreaDidChange, object: currentVideoDevice)
    NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaDidChange), name: .AVCaptureDeviceSubjectAreaDidChange, object: videoDeviceInput.device)
    
    self.session.addInput(videoDeviceInput)
    self.videoDeviceInput = videoDeviceInput
} else {
   
    self.session.addInput(self.videoDeviceInput)
}

处理中断和错误

在捕获会话期间,可能会发生中断,例如电话呼叫、来自其他应用程序的通知和音乐播放。通过添加观察员以侦听AVCaptureSessionWasInterruptedNotification来处理这些中断:

NotificationCenter.default.addObserver(self,
                                       selector: #selector(sessionWasInterrupted),
                                       name: .AVCaptureSessionWasInterrupted,
                                       object: session)
NotificationCenter.default.addObserver(self,
                                       selector: #selector(sessionInterruptionEnded),
                                       name: .AVCaptureSessionInterruptionEnded,
                                       object: session)

当AVCam收到中断通知时,它可以暂停或暂停会话,并在中断结束时选择恢复活动。AVCam将sessionWasInterrupted注册为接收通知的处理程序,以便在捕获会话中断时通知用户:

if reason == .audioDeviceInUseByAnotherClient || reason == .videoDeviceInUseByAnotherClient {
   
    showResumeButton = true
} else if reason == .videoDeviceNotAvailableWithMultipleForegroundApps {
   
    // Fade-in a label to inform the user that the camera is unavailable.
    cameraUnavailableLabel.alpha = 0
    cameraUnavailableLabel.isHidden = false
    UIView.animate(withDuration: 0.25) {
   
        self.cameraUnavailableLabel.alpha = 1
    }
} else if reason == .videoDeviceNotAvailableDueToSystemPressure {
   
    print("Session stopped running due to shutdown system pressure level.")
}

摄像机视图控制器观察AVCaptureSessionRuntimeError以在发生错误时接收通知:

NotificationCenter.default.addObserver(self,
                                       selector: #selector(sessionRuntimeError),
                                       name: .AVCaptureSessionRuntimeError,
                                       object: session)

发生运行时错误时,请重新启动捕获会话:

// If media services were reset, and the last start succeeded, restart the session.
if error.code == .mediaServicesWereReset {
   
    sessionQueue.async {
   
        if self.isSessionRunning {
   
            self.session.startRunning()
            self.isSessionRunning = self.session.isRunning
        } else {
   
            DispatchQueue.main.async {
   
                self.resumeButton.isHidden = false
            }
        }
    }
} else {
   
    resumeButton.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值