2、AVFoundation框架入门:核心类、权限与会话配置
好,咱们正式开始接触音视频开发的核心——AVFoundation。说实话,这个框架是iOS上做音视频处理绕不开的坎。我当年刚接触时,也被它庞大的类体系搞得晕头转向。但别怕,今天咱们就把最核心的几个类、权限申请和会话配置讲透。
2.1 AVFoundation核心类介绍
AVFoundation里类很多,但真正天天打交道的,其实就这几个。我个人习惯把它们分成三组:
2.1.1 设备相关:AVCaptureDevice
这个类代表物理硬件设备。摄像头、麦克风,都是它。你可以通过它查询设备是否可用、支持哪些格式、当前对焦状态等。
// 获取后置摄像头
guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video,
position: .back) else {
print("后置摄像头不可用")
return
}
嗯,这里要注意:.builtInWideAngleCamera是iOS 10之后才有的写法。老项目里可能还在用AVCaptureDevice.devices(),但那个已经废弃了。我在接手一个遗留项目时,就踩过这个坑——编译没问题,跑在iOS 14上直接崩溃。
2.1.2 输入输出:AVCaptureInput 与 AVCaptureOutput
说白了,输入就是数据从哪里来,输出就是数据到哪里去。
| 类名 | 作用 | 常见子类 |
|---|---|---|
| AVCaptureInput | 数据源,连接设备 | AVCaptureDeviceInput |
| AVCaptureOutput | 数据目的地 | AVCaptureVideoDataOutput、AVCaptureAudioDataOutput、AVCaptureMovieFileOutput |
你想想看,摄像头采集到的原始数据,通过AVCaptureDeviceInput进入系统,然后你可以选择:
- 用
AVCaptureVideoDataOutput拿到每一帧的像素数据,自己做美颜或滤镜 - 用
AVCaptureMovieFileOutput直接录制成文件,省心省力
2.1.3 会话管理:AVCaptureSession
这个是最关键的。它就像一个大管家,负责把输入和输出连接起来,管理整个采集流程。
let session = AVCaptureSession()
session.sessionPreset = .high
// 添加输入
guard let input = try? AVCaptureDeviceInput(device: backCamera) else { return }
if session.canAddInput(input) {
session.addInput(input)
}
// 添加输出
let videoOutput = AVCaptureVideoDataOutput()
if session.canAddOutput(videoOutput) {
session.addOutput(videoOutput)
}
// 启动会话
session.startRunning()
为什么说它重要?因为所有配置变更,都要通过它来协调。我曾经遇到过一个bug:在录制过程中切换摄像头,画面卡住不动。后来发现是没在beginConfiguration和commitConfiguration之间做操作。嗯,这个后面会细讲。
2.2 权限申请与配置
iOS对隐私保护非常严格。摄像头和麦克风权限,必须用户明确授权。我刚开始做直播时,就因为没有处理好权限,被App Store拒了两次。
2.2.1 Info.plist 配置
首先,你必须在Info.plist里添加描述文字。否则调用API时直接闪退,连弹窗都没有。
<key>NSCameraUsageDescription</key>
<string>我们需要使用您的摄像头来进行直播</string>
<key>NSMicrophoneUsageDescription</key>
<string>我们需要使用您的麦克风来录制声音</string>
这里有个小细节:描述文字要写清楚用途。苹果审核时会看这个,如果写得含糊不清,可能会被驳回。
2.2.2 运行时权限申请
配置好plist后,代码里还要主动申请。我个人习惯在进入直播页面时统一申请,而不是用到时才弹窗——用户体验会好很多。
import AVFoundation
// 申请摄像头权限
AVCaptureDevice.requestAccess(for: .video) { granted in
DispatchQueue.main.async {
if granted {
print("摄像头权限已获取")
} else {
print("用户拒绝了摄像头权限")
}
}
}
// 申请麦克风权限
AVCaptureDevice.requestAccess(for: .audio) { granted in
// 类似处理
}
你可能会问:如果用户第一次拒绝了怎么办?嗯,这时候可以引导用户去设置里手动开启。但注意,不能强制弹窗,苹果不允许。
DispatchQueue.main.async切回主线程再操作UI。
2.3 音视频会话配置
权限拿到后,就该配置会话了。这里涉及两个核心概念:AVCaptureSessionPreset和AVCaptureConnection。
2.3.1 预设质量等级
sessionPreset决定了采集的分辨率和帧率。常用的有:
| 预设值 | 分辨率 | 适用场景 |
|---|---|---|
| .high | 最高支持 | 通用录制 |
| .medium | 480p左右 | 网络直播 |
| .low | 360p左右 | 低带宽场景 |
| .hd1920x1080 | 1080p | 高清录制 |
但要注意:不是所有设备都支持所有预设。比如iPhone 6就不支持4K。我建议先查询设备支持列表:
let device = AVCaptureDevice.default(for: .video)!
if device.supportsSessionPreset(.hd1920x1080) {
session.sessionPreset = .hd1920x1080
} else {
session.sessionPreset = .high
}
2.3.2 配置输入输出连接
当你添加了输入和输出后,系统会自动建立连接(AVCaptureConnection)。但有时候你需要手动调整,比如设置视频方向。
// 获取视频输出连接
guard let connection = videoOutput.connection(with: .video) else { return }
// 设置视频方向为竖屏
if connection.isVideoOrientationSupported {
connection.videoOrientation = .portrait
}
// 设置视频镜像(前置摄像头需要)
if connection.isVideoMirroringSupported {
connection.isVideoMirrored = true
}
这里有个坑:connection(with: .video)可能返回nil。为什么?因为输出还没添加到session,或者session还没开始运行。我建议在session.startRunning()之后,或者至少确保输出已经被添加了,再获取连接。
2.3.3 动态调整配置
有时候你需要在运行中调整配置,比如切换摄像头。这时候要用beginConfiguration和commitConfiguration包裹起来。
session.beginConfiguration()
// 移除旧输入
session.removeInput(oldInput)
// 添加新输入
session.addInput(newInput)
// 提交配置
session.commitConfiguration()
beginConfiguration和commitConfiguration之间,可以批量修改配置。系统会等所有修改完成后,一次性应用。这样能避免中间状态导致的卡顿或崩溃。
2.4 实战:搭建一个最小采集系统
好了,理论讲完了。咱们动手搭一个最简单的摄像头采集系统。把上面讲的知识点串起来:
import AVFoundation
class CameraManager {
let session = AVCaptureSession()
private var videoInput: AVCaptureDeviceInput?
private var videoOutput: AVCaptureVideoDataOutput?
func setupCamera() {
// 1. 获取设备
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video,
position: .back) else {
print("摄像头不可用")
return
}
// 2. 创建输入
guard let input = try? AVCaptureDeviceInput(device: camera) else {
print("创建输入失败")
return
}
videoInput = input
// 3. 创建输出
let output = AVCaptureVideoDataOutput()
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "video.queue"))
videoOutput = output
// 4. 配置会话
session.beginConfiguration()
session.sessionPreset = .high
if session.canAddInput(input) {
session.addInput(input)
}
if session.canAddOutput(output) {
session.addOutput(output)
}
// 5. 设置连接属性
if let connection = output.connection(with: .video) {
if connection.isVideoOrientationSupported {
connection.videoOrientation = .portrait
}
}
session.commitConfiguration()
// 6. 启动
session.startRunning()
}
}
这段代码虽然简单,但涵盖了核心流程。你把它跑起来,就能看到摄像头开始工作了。当然,要显示画面还需要加一个AVCaptureVideoPreviewLayer,这个我们下一章再讲。
- AVCaptureDevice:代表硬件设备
- AVCaptureInput/Output:数据的输入输出
- AVCaptureSession:核心管理者
- 权限申请:Info.plist + requestAccess
- 会话配置:preset、connection、begin/commitConfiguration
嗯,今天就到这里。下一章我们会深入AVCaptureVideoDataOutput,教你如何拿到每一帧图像数据,并做实时处理。到时候咱们再聊美颜和滤镜的实现思路。