(七)深入了解AVFoundation-采集:采集系统架构与 AVCaptureSession 全面梳理
引言
在 iOS 开发中,AVFoundation 是构建音视频功能的强大底层框架。而在音视频功能中,“采集”往往是最基础也是最关键的一环。从摄像头捕捉图形、到麦克风获取声音,构建一条高效且稳定的采集链是开发高质量音视频应用的前提。
本系列将逐步深入 AVFoundation的采集机制,了解它的地方设计与实际使用。在本篇中,我们会聚集在采集系统的核心管理单元——AVCaptureSession,从整体架构出发,梳理采集链路的组成方式与搭建流程。
AVCaptureSession 的职责与系统架构
在 AVFoundation 中,AVCaptureSession 是整个采集系统的中心协调者。它的主要职责包括:
- 管理输入(Input)与输出(Output)设备:例如摄像头、麦克风作为输入,图像帧输出、音频数据输出作为输出。
- 协调数据流的流动:确保从设备捕获到的数据正确地传递到输出模块。
- 统一管理采集回话的生命周期:如开始采集startRunning()、停止采集stopRunning()、中断与恢复。
- 维护链接配置:控制比如视频方向、镜像、视频稳定等细节。
简而言之,AVCaptureSession 就像是一个数据总管,它把输入设备(比如摄像头、麦克风)和输出目标(比如屏幕预览、数据编码器)连接在一起,并统一管理整个采集链条的工作状态。
采集系统的基本架构可以理解成这样一个数据流动图。
简化来说就是:设备->输入->Session->输出->处理展示。
- 一个 AVCaptureSession 可以同时管理多个输入输出(比如前后摄像头切换,或者同时采集视频与音频)。
- 输入与输出设备必须通过 session.addInput() / session.addOutput() 正式添加到会话中,才会参与采集。
- 建议在 专用串行 DispatchQueue 中配置 Session,避免 UI 卡顿。
输入 + 输出:构建采集模型的两大核心
在 AVFoundation 采集系统中,采集链条的两大基本构成单元就是:
- 输入(Input)
- 输出(Output)
理解输入输出的工作原理和使用方式,是搭建稳定采集系统的基础。
输入(Input):采集数据的源头
输入负责吧硬件设备(如摄像头、麦克风)链接到AVCaptureSession中。
在实际开发中,我们通常就是使用AVCaptureDeviceInput 来包装摄像头和麦克风。
以摄像头为例:
// 1. 获取摄像头设备
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {fatalError("未找到后置摄像头")
}// 2. 将设备包装成输入
let cameraInput = try AVCaptureDeviceInput(device: camera)// 3. 添加到 Session
if session.canAddInput(cameraInput) {session.addInput(cameraInput)
}
注意事项:
- 添加输入前要用 canAddInput(_:) 检查是否可以添加。
- 注意捕获设备可能没有权限,需要提前请求授权。
- 创建 AVCaptureDeviceInput 可能抛异常,使用 try。
输出(Output):采集数据的去向
输出负责接收采集到的数据,并将其交给需要的地方处理,比如:
- 显示预览画面
- 实时处理图像
- 保存到文件
- 推流到服务器
输出的类型有很多种:
输出类别 | 相关类名 | 描述 |
---|---|---|
图像数据输出 | AVCaptureVideoDataOutput | 原始视频帧数据回调 |
音频数据输出 | AVCaptureAudioDataOutput | 原始音频数据回调 |
拍照输出 | AVCapturePhotoOutput | 拍摄静态照片 |
录制文件输出 | AVCaptureMovieFileOutput | 录制成视频文件 |
元数据输出(如二维码) | AVCaptureMetadataOutput | 检测并返回元数据(条形码、二维码) |
输出基本使用流程(以视频数据输出为例)
// 1. 创建输出
let videoOutput = AVCaptureVideoDataOutput()// 2. 配置输出参数(如像素格式)
videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
]// 3. 设置代理回调队列
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "VideoOutputQueue"))// 4. 添加到 Session
if session.canAddOutput(videoOutput) {session.addOutput(videoOutput)
}
代理方法示例(捕获到每一帧数据):
extension YourClass: AVCaptureVideoDataOutputSampleBufferDelegate {func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {// 处理每一帧图像}
}
注意事项:
- 回调通常发生在自定义的串行队列上,避免占用主线程。
- 输出设备也需要使用 canAddOutput(_:) 检查后再添加。
输入(Input) 负责采集硬件数据,输出(Output)负责把采集到的数据交给你处理或者保存。
输入输出就像水管的两端,中间由 AVCaptureSession 统一协调和流转。
搭建完整采集链条
在前面了解了 AVCaptureSession 的基本架构,以及输入(Input)和输出(Output)之后,这一部分,我们直接从实际角度出发,梳理一次完整搭建采集链条的流程。
完整流程
搭建完整采集链条,核心步骤可以总结为:
- 创建 AVCaptureSession
- 选择并创建输入设备(Input)
- 选择并创建输出设备(Output)
- 添加输入与输出到 Session
- 配置连接参数(如摄像头方向、镜像等)
- 创建预览层(可选)
- 启动 Session
整体的伪代码流程如下:
// 1. 创建 Session
let session = AVCaptureSession()// 2. 配置 preset
session.sessionPreset = .high// 3. 创建输入(摄像头)
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),let cameraInput = try? AVCaptureDeviceInput(device: camera),session.canAddInput(cameraInput) else {return
}
session.addInput(cameraInput)// 4. 创建输出(视频数据输出)
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "VideoOutputQueue"))
if session.canAddOutput(videoOutput) {session.addOutput(videoOutput)
}// 5. 配置连接(方向、镜像)
if let connection = videoOutput.connection(with: .video) {connection.videoOrientation = .portraitconnection.isVideoMirrored = false
}// 6. 创建预览层(可选)
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer)// 7. 启动 Session
session.startRunning()
采集分辨率的控制开关
sessionPreset 负责定义采集的数据质量,比如分辨率、码率、帧率等。
常见的 preset 有:
Preset | 说明 |
---|---|
.high | 高质量(设备自动适配) |
.medium | 中等质量,适配中速网络上传场景 |
.low | 低质量,适合弱网传输 |
.hd1280x720 | 720p 高清 |
.hd1920x1080 | 1080p 高清 |
.photo | 照片质量(高分辨率) |
注意:
- preset 需要在 startRunning() 之前设置。
- preset 要与输入设备能力匹配,否则设置无效,比如某些前置摄像头不支持 4K 采集。
- 可以使用 session.canSetSessionPreset(_:) 检查兼容性。
连接(AVCaptureConnection)管理
在 Session 里,输入与输出之间的通道,叫做 AVCaptureConnection。
连接对象允许我们进一步微调采集流的细节,比如:
- 方向(Orientation)
- 镜像(Mirrored)
- 视频防抖(Video Stabilization)
- 自动对焦/曝光/白平衡(某些连接可以关联控制)
一般情况下,我们需要手动设置采集方向为竖屏:
if let connection = videoOutput.connection(with: .video),connection.isVideoOrientationSupported {connection.videoOrientation = .portrait
}
很多 app 前置摄像头拍照是镜像模式,也可以通过 Connection 设置:
if camera.position == .front,let connection = videoOutput.connection(with: .video),connection.isVideoMirrored {connection.isVideoMirrored = true
}
线程注意点:采集是高度异步的
Session 的 startRunning 和 stopRunning 是同步调用,但异步完成,因此推荐在后台线程调用。
输出的数据回调 (captureOutput(_:didOutput:from:)) 是在你指定的 DispatchQueue里触发的,不是主线程。如果在回调里要更新 UI,需要回到主线程。
DispatchQueue.global().async {session.startRunning()
}// 回调中处理 UI
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {DispatchQueue.main.async {// 更新 UI,比如显示帧率}
}
结语
在本篇内容中,我们从整体视角出发,完整梳理了 AVCaptureSession 为核心的采集系统,包括它的职责、输入输出模型,以及如何搭建一条基本的采集链条。
同时也深入探讨了 preset 设置、连接管理 和 线程注意事项 等关键细节,帮助你在实际项目中少踩坑。
可以看到,AVFoundation 的采集体系虽然灵活强大,但也有一定的复杂度。理解 Session 是怎么组织 Input 和 Output 的,掌握 Connection 上的各种参数调优,才能真正驾驭底层采集系统。
接下来的博客我们将继续AVFoudation的采集篇章,我们将在这个基础上,进一步探索更多实战话题。