探索iOS之多摄像头预览架构
在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之多摄像头预览架构相关推荐
- 使用实时摄像头预览的iOS对象检测(六)
目录 介绍 应用布局 捕获相机反馈 相机反馈预览 完成相机预览配置 结论 总目录 将ONNX对象检测模型转换为iOS Core ML(一) 解码Core ML YOLO对象检测器(二) 使用数组操作解 ...
- 【Android RTMP】Android Camera 视频数据采集预览 ( 视频采集相关概念 | 摄像头预览参数设置 | 摄像头预览数据回调接口 )
文章目录 安卓直播推流专栏博客总结 一. Android 端数据采集涉及到的相关概念 二. Camera 预览图像尺寸设置 三. 获取摄像头采集的数据格式 安卓直播推流专栏博客总结 Android R ...
- Android 短视频编辑开发之摄像头预览实时美颜(三)
前言: 在上一篇文章中给小伙伴们介绍了进行Camera预览,如果你还没有看过的话,建议先去看上一篇文章<Android 短视频开发之摄像头预览(二> 本篇文章会介绍如何实现摄像头预览画面实 ...
- 苹果于近日推送了 iOS 14.5 开发者预览版 Beta
导读 近日,苹果推送了 iOS 14.5 开发者预览版 Beta 5 .除日常修修补补外,引入了多项重要的新功能,包括,这也是 iOS 14 迄今为止最大的一次更新.此外,iPadOS 14.5 与w ...
- Android 前置摄像头预览与编码
Android Camera前置摄像头采集.基于android.hardware.Camera,已经提示过时. 目标:在前置摄像头预览过程中,采集预览数据并编码到本地. 1. 设置摄像头的预览 获取摄 ...
- USB摄像头预览识别二维码
博客: 安卓之家 掘金: jp1017 微博: 追风917 CSDN: 蒋朋的家 简书: 追风917 前言 二维码现在用的超级多,其实它就是一种编码,把字符串编码保存成一个图片,我们扫描图片得到字符串 ...
- 二、JAVA调用海康威视SDK实现摄像头预览完整版
接上一章:一.JAVA调用海康威视SDK实现摄像头预览 添加摄像头信息输入框 添加视频控制按钮 添加截图功能 代码: PreView.java package com.kx.hcws;import j ...
- android预览摄像头,Android上的摄像头预览处理
我正在Android上为我的机器人制作线路跟随者(学习Java/Android编程),目前我正面临图像处理问题:摄像头预览返回一个名为YUV的图像格式,I想要将其转换为阈值以便知道线路的位置,那么该怎 ...
- 基于camera2 untiy悬浮窗摄像头预览分析
前言 在某机系统上 unity 导出的android 工程 使用camerax 悬浮窗口 bindToLifecycle 加上预览界面就显示不出来(手机上没问题,原因没找到),不得以 改用camera ...
最新文章
- 软件工程导论结对项目
- 学习一个 Linux 命令:sort 命令
- 使用SAP WebIDE往Github上推送代码修改时遇到错误消息 Commit request failed Commit failed. Ref must be HEAD and is HEAD
- 二维与三维之间的桥梁——点云
- oracle 压力测试工具benchmarksql
- cf round480D Perfect Groups
- Introduce Local Extension
- 【DVRP】基于matlab蚁群算法求解带距离的VRP问题【含Matlab源码 1040期】
- svn如何退回软件版本_SVN版本控制工具的使用
- 设计类毕业生求职指南!手把手帮你从零开始找到工作!
- WMS仓库仓储管理系统源码
- P2010 [NOIP2016 普及组] 回文日期
- 广域网宽带接入技术一
- 网关是什么?工业网关是什么?
- 单声道蓝牙实现音乐播放
- 吴恩达机器学习ex1——通过人口预测小摊经济状况
- Java IO框架之BIO、NIO、AIO
- 实现文件导出功能(Excel文件形式):全部导出
- ABP中的AsyncCrudAppService介绍
- Linux内核源码目录介绍