在iOS13.0开始支持多摄像头预览AVCaptureMultiCamSession,然后iOS15.0增加支持摄像头画中画预览。在使用之前,我们通过isMultiCamSupported()判断是否支持多Camera同时预览。让我们先看下效果:

一、Camera架构

1、Camera流水线

Camera由AVCaptureDeviceInput、AVCaptureSession、AVCaptureOutput构成。如下图所示:

2、单Camera架构

单个Camera架构意味只有一个AVCaptureDeviceInput,同步输出VideoData和DepthData,支持预览和输出文件。如下图所示:

3、多Camera架构

与单Camera架构相比,多Camera架构包括多个输入源AVCaptureDeviceInput,多个摄像头同时预览,如下图所示:

二、Camera类图结构

Camera类图包括AVCaptureDeviceInput、AVCaptureMultiCamSession、AVCaptureVideoDataOutput、AVCaptureVideoPreviewLayer、AVAssetWriter。如下图所示:

三、Camera输入输出

Camera的输入包括:前置Camera、后置Camera、麦克风,输出包括:预览数据、图片、文件、Metadata,由AVCaptureMultiCamSession进行管理。如下图所示:

四、MultiCamera流同步

多个Camera同时预览,它们共享分辨率和帧率。也需要进行流同步,包括如下:

  • 曝光
  • 对焦
  • 白平衡

五、Camera画中画预览

1、初始化

初始化阶段,主要设置预览图层、配置capture session,示例代码如下:

 override func viewDidLoad() {super.viewDidLoad()// 设置前置、后置camera预览图层backCameraVideoPreviewView.videoPreviewLayer.setSessionWithNoConnection(session)frontCameraVideoPreviewView.videoPreviewLayer.setSessionWithNoConnection(session)// 配置 capture sessionsessionQueue.async {self.configureSession()}}

2、配置session

配置capture session的示例代码如下:

 private func configureSession() {guard setupResult == .success else { return }guard AVCaptureMultiCamSession.isMultiCamSupported else {print("MultiCam not supported on this device")setupResult = .multiCamNotSupportedreturn}session.beginConfiguration()defer {session.commitConfiguration()if setupResult == .success {checkSystemCost()}}guard configureBackCamera() else {setupResult = .configurationFailedreturn}guard configureFrontCamera() else {setupResult = .configurationFailedreturn}}

3、配置后置Camera

配置流程包括:查找后置Camera、添加到session、连接输入设备到输出数据、连接输入设备到预览图层等,示例代码如下:

 private func configureBackCamera() -> Bool {session.beginConfiguration()defer {session.commitConfiguration()}// 查找后置cameraguard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {print("Could not find the back camera")return false}// 添加后置camera到sessiondo {backCameraDeviceInput = try AVCaptureDeviceInput(device: backCamera)guard let backCameraDeviceInput = backCameraDeviceInput,session.canAddInput(backCameraDeviceInput) else {return false}session.addInputWithNoConnections(backCameraDeviceInput)} catch {print("Could not create back camera device input: \(error)")return false}// 查找后置camera输入视频端口guard let backCameraDeviceInput = backCameraDeviceInput,let backCameraVideoPort = backCameraDeviceInput.ports(for: .video,sourceDeviceType: backCamera.deviceType,sourceDevicePosition: backCamera.position).first else {return false}// 添加后置camera输出视频数据guard session.canAddOutput(backCameraVideoDataOutput) else {print("Could not add the back camera video data output")return false}session.addOutputWithNoConnections(backCameraVideoDataOutput)backCameraVideoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)]backCameraVideoDataOutput.setSampleBufferDelegate(self, queue: dataOutputQueue)// 连接后置camera输入到数据输出let backCameraVideoDataOutputConnection = AVCaptureConnection(inputPorts: [backCameraVideoPort],output: backCameraVideoDataOutput)guard session.canAddConnection(backCameraVideoDataOutputConnection) else {print("Could not add a connection to the back camera video data output")return false}session.addConnection(backCameraVideoDataOutputConnection)backCameraVideoDataOutputConnection.videoOrientation = .portrait// 连接后置camera输入到预览图层guard let backCameraVideoPreviewLayer = backCameraVideoPreviewLayer else {return false}let backCameraVideoPreviewLayerConnection = AVCaptureConnection(inputPort: backCameraVideoPort, videoPreviewLayer: backCameraVideoPreviewLayer)guard session.canAddConnection(backCameraVideoPreviewLayerConnection) else {print("Could not add a connection to the back camera video preview layer")return false}session.addConnection(backCameraVideoPreviewLayerConnection)return true}

4、配置前置Camera

前置Camera与后置的配置流程类似,只是把back换成front。另外,前置Camera开启镜像。示例代码如下:

    private func configureFrontCamera() -> Bool {session.beginConfiguration()defer {session.commitConfiguration()}// 查找前置cameraguard let frontCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else {print("Could not find the front camera")return false}// 添加前置camera到sessiondo {frontCameraDeviceInput = try AVCaptureDeviceInput(device: frontCamera)guard let frontCameraDeviceInput = frontCameraDeviceInput,session.canAddInput(frontCameraDeviceInput) else {return false}session.addInputWithNoConnections(frontCameraDeviceInput)} catch {print("Could not create front camera device input: \(error)")return false}// 查找前置camera输入视频端口guard let frontCameraDeviceInput = frontCameraDeviceInput,let frontCameraVideoPort = frontCameraDeviceInput.ports(for: .video,sourceDeviceType: frontCamera.deviceType,sourceDevicePosition: frontCamera.position).first else {return false}// 添加前置camera输出视频数据guard session.canAddOutput(frontCameraVideoDataOutput) else {print("Could not add the front camera video data output")return false}session.addOutputWithNoConnections(frontCameraVideoDataOutput)frontCameraVideoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)]frontCameraVideoDataOutput.setSampleBufferDelegate(self, queue: dataOutputQueue)// 连接前置camera输入到数据输出let frontCameraVideoDataOutputConnection = AVCaptureConnection(inputPorts: [frontCameraVideoPort],output: frontCameraVideoDataOutput)guard session.canAddConnection(frontCameraVideoDataOutputConnection) else {print("Could not add a connection to the front camera video data output")return false}session.addConnection(frontCameraVideoDataOutputConnection)frontCameraVideoDataOutputConnection.videoOrientation = .portrait// 连接前置camera输入到预览图层guard let frontCameraVideoPreviewLayer = frontCameraVideoPreviewLayer else {return false}let frontCameraVideoPreviewLayerConnection = AVCaptureConnection(inputPort: frontCameraVideoPort, videoPreviewLayer: frontCameraVideoPreviewLayer)guard session.canAddConnection(frontCameraVideoPreviewLayerConnection) else {print("Could not add a connection to the front camera video preview layer")return false}session.addConnection(frontCameraVideoPreviewLayerConnection)// 前置camera开启镜像frontCameraVideoPreviewLayerConnection.isVideoMirrored = truefrontCameraVideoPreviewLayerConnection.automaticallyAdjustsVideoMirroring = falsereturn true}

5、配置双向麦克风

除了提供画中画Camera,还提供前后双向麦克风。示例代码如下:

 private func configureMicrophone() -> Bool {session.beginConfiguration()defer {session.commitConfiguration()}// 查找麦克风guard let microphone = AVCaptureDevice.default(for: .audio) else {print("Could not find the microphone")return false}// 添加麦克风到sessiondo {microphoneDeviceInput = try AVCaptureDeviceInput(device: microphone)guard let microphoneDeviceInput = microphoneDeviceInput,session.canAddInput(microphoneDeviceInput) else {return false}session.addInputWithNoConnections(microphoneDeviceInput)} catch {print("Could not create microphone input: \(error)")return false}// 查找输入设备的后置音频端口guard let microphoneDeviceInput = microphoneDeviceInput,let backMicrophonePort = microphoneDeviceInput.ports(for: .audio,sourceDeviceType: microphone.deviceType,sourceDevicePosition: .back).first else {return false}// 查找输入设备的前置音频端口guard let frontMicrophonePort = microphoneDeviceInput.ports(for: .audio,sourceDeviceType: microphone.deviceType,sourceDevicePosition: .front).first else {return false}// 添加后置麦克风到输出数据guard session.canAddOutput(backMicrophoneAudioDataOutput) else {print("Could not add the back microphone audio data output")return false}session.addOutputWithNoConnections(backMicrophoneAudioDataOutput)backMicrophoneAudioDataOutput.setSampleBufferDelegate(self, queue: dataOutputQueue)// 添加前置麦克风到输出数据guard session.canAddOutput(frontMicrophoneAudioDataOutput) else {print("Could not add the front microphone audio data output")return false}session.addOutputWithNoConnections(frontMicrophoneAudioDataOutput)frontMicrophoneAudioDataOutput.setSampleBufferDelegate(self, queue: dataOutputQueue)// 连接后置麦克风到输出数据let backMicrophoneAudioDataOutputConnection = AVCaptureConnection(inputPorts: [backMicrophonePort],output: backMicrophoneAudioDataOutput)guard session.canAddConnection(backMicrophoneAudioDataOutputConnection) else {print("Could not add a connection to the back microphone audio data output")return false}session.addConnection(backMicrophoneAudioDataOutputConnection)// 连接前置麦克风到输出数据let frontMicrophoneAudioDataOutputConnection = AVCaptureConnection(inputPorts: [frontMicrophonePort],output: frontMicrophoneAudioDataOutput)guard session.canAddConnection(frontMicrophoneAudioDataOutputConnection) else {print("Could not add a connection to the front microphone audio data output")return false}session.addConnection(frontMicrophoneAudioDataOutputConnection)return true}

六、降低功耗

iOS提供API获取硬件功耗:

var hardwareCost: Float { get } // 取值[0.0, 1.0]

同时提供API获取系统压力功耗:

var systemPressureCost: Float { get } // 取值[0.0, 1.0]

关于降低功耗的可行方案如下:

  • 设置最大帧率
  • 降低Camera分辨率
  • 选择低精度像素格式

探索iOS之多摄像头预览架构相关推荐

  1. 使用实时摄像头预览的iOS对象检测(六)

    目录 介绍 应用布局 捕获相机反馈 相机反馈预览 完成相机预览配置 结论 总目录 将ONNX对象检测模型转换为iOS Core ML(一) 解码Core ML YOLO对象检测器(二) 使用数组操作解 ...

  2. 【Android RTMP】Android Camera 视频数据采集预览 ( 视频采集相关概念 | 摄像头预览参数设置 | 摄像头预览数据回调接口 )

    文章目录 安卓直播推流专栏博客总结 一. Android 端数据采集涉及到的相关概念 二. Camera 预览图像尺寸设置 三. 获取摄像头采集的数据格式 安卓直播推流专栏博客总结 Android R ...

  3. Android 短视频编辑开发之摄像头预览实时美颜(三)

    前言: 在上一篇文章中给小伙伴们介绍了进行Camera预览,如果你还没有看过的话,建议先去看上一篇文章<Android 短视频开发之摄像头预览(二> 本篇文章会介绍如何实现摄像头预览画面实 ...

  4. 苹果于近日推送了 iOS 14.5 开发者预览版 Beta

    导读 近日,苹果推送了 iOS 14.5 开发者预览版 Beta 5 .除日常修修补补外,引入了多项重要的新功能,包括,这也是 iOS 14 迄今为止最大的一次更新.此外,iPadOS 14.5 与w ...

  5. Android 前置摄像头预览与编码

    Android Camera前置摄像头采集.基于android.hardware.Camera,已经提示过时. 目标:在前置摄像头预览过程中,采集预览数据并编码到本地. 1. 设置摄像头的预览 获取摄 ...

  6. USB摄像头预览识别二维码

    博客: 安卓之家 掘金: jp1017 微博: 追风917 CSDN: 蒋朋的家 简书: 追风917 前言 二维码现在用的超级多,其实它就是一种编码,把字符串编码保存成一个图片,我们扫描图片得到字符串 ...

  7. 二、JAVA调用海康威视SDK实现摄像头预览完整版

    接上一章:一.JAVA调用海康威视SDK实现摄像头预览 添加摄像头信息输入框 添加视频控制按钮 添加截图功能 代码: PreView.java package com.kx.hcws;import j ...

  8. android预览摄像头,Android上的摄像头预览处理

    我正在Android上为我的机器人制作线路跟随者(学习Java/Android编程),目前我正面临图像处理问题:摄像头预览返回一个名为YUV的图像格式,I想要将其转换为阈值以便知道线路的位置,那么该怎 ...

  9. 基于camera2 untiy悬浮窗摄像头预览分析

    前言 在某机系统上 unity 导出的android 工程 使用camerax 悬浮窗口 bindToLifecycle 加上预览界面就显示不出来(手机上没问题,原因没找到),不得以 改用camera ...

最新文章

  1. 软件工程导论结对项目
  2. 学习一个 Linux 命令:sort 命令
  3. 使用SAP WebIDE往Github上推送代码修改时遇到错误消息 Commit request failed Commit failed. Ref must be HEAD and is HEAD
  4. 二维与三维之间的桥梁——点云
  5. oracle 压力测试工具benchmarksql
  6. cf round480D Perfect Groups
  7. Introduce Local Extension
  8. 【DVRP】基于matlab蚁群算法求解带距离的VRP问题【含Matlab源码 1040期】
  9. svn如何退回软件版本_SVN版本控制工具的使用
  10. 设计类毕业生求职指南!手把手帮你从零开始找到工作!
  11. WMS仓库仓储管理系统源码
  12. P2010 [NOIP2016 普及组] 回文日期
  13. 广域网宽带接入技术一
  14. 网关是什么?工业网关是什么?
  15. 单声道蓝牙实现音乐播放
  16. 吴恩达机器学习ex1——通过人口预测小摊经济状况
  17. Java IO框架之BIO、NIO、AIO
  18. 实现文件导出功能(Excel文件形式):全部导出
  19. ABP中的AsyncCrudAppService介绍
  20. Linux内核源码目录介绍

热门文章

  1. jdbc的事务自动提交和手动提交,以及mybatis开启自动提交后是否会复用一个连接的验证
  2. 读书-《穷查理宝典》
  3. 爬虫登陆实战 --- QQ音乐扫码登陆!真不难!
  4. Pycharm 被低估了的 10 个快捷键
  5. libpng库的生成
  6. 06Ni9DR钢板、9Ni钢
  7. Android 融云IM集成以及使用详解(二)
  8. 中国电信天翼宽带无线路由器设置wifi笔记
  9. 当当网Day2-Day3
  10. jQuery(三)—节点操作和元素尺寸