
引言
在移动应用中,实时视频处理已成为视频拍摄、短视频、直播、美颜相机等功能的核心技术之一。从简单的滤镜叠加,到复杂的美颜、AR 特效,背后都离不开对每一帧图像的高效采集与处理。在前几篇文章中,我们已经实现了基本的视频采集、人脸识别等功能,而本篇将迈出更进一步的一步 ------ 实时处理每一帧图像,打造动态视觉效果。
本篇内容将围绕 AVCaptureVideoDataOutput 展开,讲解如何获取原始视频帧,并借助 CoreImage 或 Metal 实现滤镜、美颜等实时图像处理效果。最后,我们还将以一个"实时美颜相机"为示例,串联起采集、处理与渲染的完整流程,帮助你搭建具备实用价值的实时视频处理系统。
采集视频帧:AVCaptureVideoDataOutput
在使用 AVFoundation 进行图像采集时,无论是拍照、录像、还是视频帧处理,整体的配置流程几乎一致。我们依然需要:
- 创建 AVCaptureSession。
- 添加输入设备(通常是摄像头)。
- 添加输出对象。
- 启动会话。
唯一的区别在于 输出类型的不同 。在拍照场景中,我们使用的是 AVCapturePhotoOutput;录制视频则使用 AVCaptureMovieFileOutput。而本篇重点关注的实时视频帧处理 ,需要使用的是:AVCaptureVideoDataOutput。
AVCaptureVideoDataOutput 负责将摄像头捕捉到的原始帧(CVPixelBuffer)逐帧输出给我们,这种输出是"实时的",每一帧都会通过代理方法交付给我们进行处理,非常适合用于:
- 添加滤镜
- 美颜处理
- 实时图像识别
类的基本结构
我们先来看一下 PHCaptureVideoController 的基本结构:
Swift
import UIKit
import AVFoundation
class PHCaptureVideoController: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
/// 会话
let session = AVCaptureSession()
/// 输出
private let videoDataOutput = AVCaptureVideoDataOutput()
/// 输入
private var captureDeviceInput: AVCaptureDeviceInput?
/// 队列
private let sessionQueue = DispatchQueue(label: "com.example.captureSession")
/// 代理
weak var delegate: PHCaptureProtocol?
/// 配置会话
func setupConfigureSession() {
session.beginConfiguration()
// 1.设置会话预设
setupSessionPreset()
// 2.设置会话输入
if !setupSessionInput(device: self.getDefaultCameraDevice()) {
delegate?.captureError(NSError(domain: "PHCaptureController", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Failed to add input"]))
return
}
// 3.设置会话输出
if !setupSessionOutput() {
delegate?.captureError(NSError(domain: "PHCaptureController", code: 1002, userInfo: [NSLocalizedDescriptionKey: "Failed to add output"]))
return
}
session.commitConfiguration()
}
/// 设置会话话预设
private func setupSessionPreset() {
session.sessionPreset = .high
}
/// 设置会话输入
private func setupSessionInput(device: AVCaptureDevice? = nil) -> Bool {
return true
}
/// 设置会话输出
private func setupSessionOutput() -> Bool {
return true
}
/// 启动会话
func startSession() {
}
/// 停止会话
func stopSession() {
}
//MARK: private
/// 获取默认摄像头
private func getDefaultCameraDevice() -> AVCaptureDevice? {
return getCameraDevice(position: .back)
}
/// 获取指定摄像头
private func getCameraDevice(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
let devices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position).devices
return devices.first
}
//MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
/// 捕获输出
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
// 处理视频数据
delegate?.captureVideo(sampleBuffer)
}
/// 捕获输出丢失
func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
}
}
整个类中划分为了几个方法:
- 配置会话。
- 设置会话输入。
- 设置会话输出。
- 启动会话、停止会话。
- 捕捉到视频帧的回调、丢失视频帧的回调。
配置会话
我们在 setupConfigureSession 方法中需要完成三个操作:
- 设置会话预设。
- 添加摄像头输入。
- 添加照片输出。
并且保证这些操作需要在会话 beginConfiguration 与 commitConfiguration 方法之间执行。
Swift
/// 配置会话
func setupConfigureSession() {
session.beginConfiguration()
// 1.设置会话预设
setupSessionPreset()
// 2.设置会话输入
if !setupSessionInput(device: self.getDefaultCameraDevice()) {
delegate?.captureError(NSError(domain: "PHCaptureController", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Failed to add input"]))
return
}
// 3.设置会话输出
if !setupSessionOutput() {
delegate?.captureError(NSError(domain: "PHCaptureController", code: 1002, userInfo: [NSLocalizedDescriptionKey: "Failed to add output"]))
return
}
session.commitConfiguration()
}
Swift
/// 设置会话话预设
private func setupSessionPreset() {
session.sessionPreset = .high
}
会话输入
添加摄像头设备,并包装一层 AVCaptureDeviceInput 添加到会话中。
Swift
/// 设置会话输入
private func setupSessionInput(device: AVCaptureDevice? = nil) -> Bool {
// 1.获取摄像头
guard let device = device else { return false }
do {
captureDeviceInput = try AVCaptureDeviceInput(device: device)
if session.canAddInput(captureDeviceInput!) {
session.addInput(captureDeviceInput!)
} else {
return false
}
} catch {
delegate?.captureError(error)
return false
}
return true
}
会话输出
会话输出使用AVCaptureVideoDataOutput输出,设置像素合适以及检查是否可以添加。
Swift
/// 设置会话输出
private func setupSessionOutput() -> Bool {
videoDataOutput.videoSettings = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA
]
videoDataOutput.setSampleBufferDelegate(self, queue: sessionQueue)
if session.canAddOutput(videoDataOutput) {
session.addOutput(videoDataOutput)
} else {
return false
}
return true
}
- 设置像素格式:我们选择 kCVPixelFormatType_32BGRA,这是 CoreImage 和 Metal 最常用、兼容性最强的格式;
- 设置代理与处理队列:setSampleBufferDelegate(_:queue:) 会将每一帧回调给你指定的队列处理,避免阻塞主线程;
- 检查输出是否可添加:通过 canAddOutput 判断 session 是否支持添加该输出类型,确保稳定性。
启动、停止会话
在自定义的串行队列中执行启动和停止会话。
Swift
/// 启动会话
func startSession() {
sessionQueue.async {
if !self.session.isRunning {
self.session.startRunning()
}
}
}
/// 停止会话
func stopSession() {
sessionQueue.async {
if self.session.isRunning {
self.session.stopRunning()
}
}
}
视频帧数据处理
接下来我需要实现AVCaptureVideoDataOutputSampleBufferDelegate的代理方法,并处理回调中的视频数据。
回调方法
AVCaptureVideoDataOutputSampleBufferDelegate提供了两个代理方法,一个用于捕获实时输出的视频帧数据,一个用来捕获丢失的帧数据。
我们在捕获视频帧数据的方法中将 CMSampleBuffer 数据回调到视图控制器。
Swift
//MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
/// 捕获输出
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
// 处理视频数据
delegate?.captureVideo(sampleBuffer)
}
添加滤镜
在这里我们就采用一个最简单的方式为实时预览添加一个滤镜,通过CIFilter来创建。它支持很多类型的滤镜,比如颜色翻转、漫画风格、色彩分层、像素化等等。
Swift
// 视频帧
func captureVideo(_ sampleBuffer: CMSampleBuffer) {
// 处理视频帧
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
// 处理像素缓冲区
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
// 添加滤镜
let filter = CIFilter(name: "CIComicEffect")
filter?.setValue(ciImage, forKey: kCIInputImageKey)
guard let outputImage = filter?.outputImage,
let cgImage = ciContext.createCGImage(outputImage, from: ciImage.extent) else {
return
}
let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right)
// 显示到预览图层
DispatchQueue.main.async {
self.previewImageView.image = uiImage
}
}
最终效果如下:

结语
通过本文,我们实现了使用 AVCaptureVideoDataOutput 获取原始视频帧,并结合 Core Image 对其进行实时处理的完整流程。无论是美颜、滤镜,还是图像分析,这种方式都为实时图像处理提供了极大的灵活性和可扩展性。
不过,需要注意的是,CoreImage 虽然上手简单、易于调试,但在处理高分辨率视频帧或多个滤镜叠加时,性能可能会成为瓶颈。如果你希望在性能上进一步优化,或者实现更加复杂、专业的图像处理效果,推荐使用更底层的图形处理框架,例如 Metal 或 OpenGLES,它们可以更精细地控制渲染流程、内存使用和 GPU 资源调度,是构建高性能视频应用的不二之选。