引言
在 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的采集篇章,我們將在這個基礎上,進一步探索更多實戰話題。