当前位置: 首页 > news >正文

(十三)深入了解AVFoundation-采集:视频帧采集与实时滤镜处理

引言

在移动应用中,实时视频处理已成为视频拍摄、短视频、直播、美颜相机等功能的核心技术之一。从简单的滤镜叠加,到复杂的美颜、AR 特效,背后都离不开对每一帧图像的高效采集与处理。在前几篇文章中,我们已经实现了基本的视频采集、人脸识别等功能,而本篇将迈出更进一步的一步 —— 实时处理每一帧图像,打造动态视觉效果

本篇内容将围绕 AVCaptureVideoDataOutput 展开,讲解如何获取原始视频帧,并借助 CoreImage 或 Metal 实现滤镜、美颜等实时图像处理效果。最后,我们还将以一个“实时美颜相机”为示例,串联起采集、处理与渲染的完整流程,帮助你搭建具备实用价值的实时视频处理系统。

采集视频帧:AVCaptureVideoDataOutput

在使用 AVFoundation 进行图像采集时,无论是拍照、录像、还是视频帧处理,整体的配置流程几乎一致。我们依然需要:

  1. 创建 AVCaptureSession。
  2. 添加输入设备(通常是摄像头)。
  3. 添加输出对象。
  4. 启动会话。

唯一的区别在于 输出类型的不同。在拍照场景中,我们使用的是 AVCapturePhotoOutput;录制视频则使用 AVCaptureMovieFileOutput。而本篇重点关注的实时视频帧处理,需要使用的是:AVCaptureVideoDataOutput。

AVCaptureVideoDataOutput 负责将摄像头捕捉到的原始帧(CVPixelBuffer)逐帧输出给我们,这种输出是“实时的”,每一帧都会通过代理方法交付给我们进行处理,非常适合用于:

  • 添加滤镜
  • 美颜处理
  • 实时图像识别

类的基本结构

我们先来看一下 PHCaptureVideoController 的基本结构:

import UIKit
import AVFoundationclass 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).devicesreturn 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) {}}

整个类中划分为了几个方法:

  1. 配置会话。
  2. 设置会话输入。
  3. 设置会话输出。
  4. 启动会话、停止会话。
  5. 捕捉到视频帧的回调、丢失视频帧的回调。

配置会话

我们在 setupConfigureSession 方法中需要完成三个操作:

  1. 设置会话预设。
  2. 添加摄像头输入。
  3. 添加照片输出。

并且保证这些操作需要在会话 beginConfiguration 与 commitConfiguration 方法之间执行。

    /// 配置会话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}

会话输入

添加摄像头设备,并包装一层 AVCaptureDeviceInput 添加到会话中。

    /// 设置会话输入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输出,设置像素合适以及检查是否可以添加。

/// 设置会话输出
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
}
  1. 设置像素格式:我们选择 kCVPixelFormatType_32BGRA,这是 CoreImage 和 Metal 最常用、兼容性最强的格式;
  2. 设置代理与处理队列:setSampleBufferDelegate(_:queue:) 会将每一帧回调给你指定的队列处理,避免阻塞主线程;
  3. 检查输出是否可添加:通过 canAddOutput 判断 session 是否支持添加该输出类型,确保稳定性。

启动、停止会话

在自定义的串行队列中执行启动和停止会话。

    /// 启动会话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 数据回调到视图控制器。

    //MARK: - AVCaptureVideoDataOutputSampleBufferDelegate/// 捕获输出func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {// 处理视频数据delegate?.captureVideo(sampleBuffer)}

添加滤镜

在这里我们就采用一个最简单的方式为实时预览添加一个滤镜,通过CIFilter来创建。它支持很多类型的滤镜,比如颜色翻转、漫画风格、色彩分层、像素化等等。

    // 视频帧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 资源调度,是构建高性能视频应用的不二之选。

http://www.xdnf.cn/news/547093.html

相关文章:

  • Https流式输出一次输出一大段,一卡一卡的-解决方案
  • 海康工业相机白平衡比选择器对应的值被重置后,如何恢复原成像
  • 【Unity】DOTween的常用函数解释
  • Java 大视界——Java大数据在智慧交通智能停车诱导系统中的数据融合与实时更新
  • 【数据结构】队列的完整实现
  • 五:操作系统内存管理之连续内存分配
  • 机器学习10-随机森林
  • 解决将cpp编译为dll,使用ctype的cdll调用,printf不输出在终端
  • 算法与数据结构:位运算与快速幂
  • 地理信息数据格式.GeoJSON数据格式介绍
  • 无人机避障——深蓝学院浙大Fast-planner学习部分(采用均匀B-Spline和非均匀B-Spline进行轨迹优化和时间重分配)
  • 力扣-盛最多水的容器
  • 网络刷卡器的分类和网口通讯流程
  • hghac集群服务器时间同步(chrony同步)
  • 替换word中的excel
  • 【25软考网工】第七章 (2)UOS Linux文件和目录管理、用户和组管理
  • 音频应用的MediaSession冲突
  • Transfomer学习
  • Java NIO(New I/O)
  • ubuntu kubeasz 部署高可用k8s 集群
  • k8s1.27版本集群部署minio分布式
  • 01 基本介绍及Pod基础
  • 【DCGMI专题1】---DCGMI 在 Ubuntu 22.04 上的深度安装指南与原理分析(含架构图解)
  • 深度学习架构快速入门——卷积神经网络CNN、循环神经网络RNN、生成对抗网络GAN、Transformer以及编码器-解码器
  • Jenkins:自动化之魂,解锁高效开发的密钥
  • 2025-05-20 模型下载--文本向量化--Faiss检索
  • SQLMesh 内置宏详解:@PIVOT等常用宏的核心用法与示例
  • Qt文件:XML文件
  • 战略游戏--树形dp
  • Java中字符串(String类)的常用方法