概述
- 音視頻采集是直播架構(gòu)的第一環(huán)敬特,是視頻的來源
- 其實(shí)視頻的采集有多個(gè)應(yīng)用場(chǎng)景:比如二維碼開發(fā)
- 音視頻采集包括兩部分:
- 視頻采集
- 音頻采集
- 在iOS開發(fā)中揽咕,是可以同步采集視頻&音頻的璃搜,使用方式也非常簡單
- 相關(guān)的采集API都封裝在AVFoundation框架中,導(dǎo)入對(duì)應(yīng)框架,實(shí)現(xiàn)功能即可
采集步驟:
采集步驟文字描述
-
導(dǎo)入框架
- 相關(guān)API主要在AVFoundation框架中羞海,因此需要先導(dǎo)入框架
-
創(chuàng)建捕捉會(huì)話(AVCaptureSession)
- 該會(huì)話用于連接之后的輸入源&輸出源
- 輸入源:攝像頭&話筒
- 輸出源:拿到對(duì)應(yīng)的音頻&視頻數(shù)據(jù)的出口
- 會(huì)話:用于將輸入源&輸出源連接起來
-
設(shè)置視頻輸入源&輸出源
- 輸入源(AVCaptureDeviceInput):從攝像頭輸入
- 輸出源(AVCaptureVideoDataOutput):可以設(shè)置代理,在代理方法中拿到數(shù)據(jù)
- 將輸入&輸出添加到會(huì)話中
-
設(shè)置音頻輸入源&輸出源
- 輸入源(AVCaptureDeviceInput):從話筒輸入
- 輸出源(AVCaptureAudioDataOutput):可以設(shè)置代理曲管,在代理方法中拿到數(shù)據(jù)
- 將輸入&輸出添加到會(huì)話中
-
添加預(yù)覽圖層(可選)
- 如果希望用戶看到采集的畫面却邓,可以添加預(yù)覽圖層
- 該預(yù)覽圖層不是必須的,及時(shí)沒有添加也可以正常采集數(shù)據(jù)
-
開始采集即可
- 調(diào)用會(huì)話(AVCaptureSession)的startRunning方法即可開始采集
代碼解析
- 調(diào)用會(huì)話(AVCaptureSession)的startRunning方法即可開始采集
整體代碼步驟:
- 函數(shù)一(設(shè)置視頻輸入輸出)
- 函數(shù)二(設(shè)置音頻輸入輸出)
- 添加預(yù)覽圖層
- 遵守協(xié)議院水,實(shí)現(xiàn)代理方法
實(shí)現(xiàn)代碼:
- 整體步驟代碼
// 1.創(chuàng)建捕捉會(huì)話
let session = AVCaptureSession()
// 2.設(shè)置視頻輸入輸出
setupVideoSource(session: session)
// 3.設(shè)置音頻輸入輸出
setupAudioSource(session: session)
// 4.添加預(yù)覽圖層
setupPreviewLayer(session: session)
// 5.開始掃描
session.startRunning()
- 函數(shù)一(設(shè)置視頻輸入輸出)
// 給會(huì)話設(shè)置視頻源(輸入源&輸出源)
fileprivate func setupVideoSource(session : AVCaptureSession) {
// 1.創(chuàng)建輸入
// 1.1.獲取所有的設(shè)備(包括前置&后置攝像頭)
guard let devices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as? [AVCaptureDevice] else { return }
// 1.2.取出獲取前置攝像頭
let d = devices.filter({ return $0.position == .front }).first
// 1.3.通過前置攝像頭創(chuàng)建輸入設(shè)備
guard let videoInput = try? AVCaptureDeviceInput(device: d) else { return }
// 2.創(chuàng)建輸出源
// 2.1.創(chuàng)建視頻輸出源
let videoOutput = AVCaptureVideoDataOutput()
// 2.2.設(shè)置代理,以及代理方法的執(zhí)行隊(duì)列(在代理方法中拿到采集到的數(shù)據(jù))
let queue = DispatchQueue.global()
videoOutput.setSampleBufferDelegate(self, queue: queue)
// 3.將輸入&輸出添加到會(huì)話中
// 3.1.添加輸入源
if session.canAddInput(videoInput) {
session.addInput(videoInput)
}
// 3.2.添加輸出源
if session.canAddOutput(videoOutput) {
session.addOutput(videoOutput)
}
// 4.給connect賦值
videoConnect = videoOutput.connection(withMediaType: AVMediaTypeVideo)
}
- 函數(shù)二(設(shè)置音頻輸入輸出)
// 給會(huì)話設(shè)置音頻源(輸入源&輸出源)
fileprivate func setupAudioSource(session : AVCaptureSession) {
// 1.創(chuàng)建輸入
guard let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio) else { return }
guard let audioInput = try? AVCaptureDeviceInput(device: device) else { return }
// 2.創(chuàng)建輸出源
let audioOutput = AVCaptureAudioDataOutput()
let queue = DispatchQueue.global()
audioOutput.setSampleBufferDelegate(self, queue: queue)
// 3.將輸入&輸出添加到會(huì)話中
if session.canAddInput(audioInput) {
session.addInput(audioInput)
}
if session.canAddOutput(audioOutput) {
session.addOutput(audioOutput)
}
}
- 添加預(yù)覽圖
// 添加預(yù)覽圖層
fileprivate func setupPreviewLayer(session : AVCaptureSession) {
// 1.創(chuàng)建預(yù)覽圖層
guard let previewLayer = AVCaptureVideoPreviewLayer(session: session) else { return }
// 2.設(shè)置圖層的屬性
previewLayer.frame = view.bounds
// 3.將圖層添加到view中
view.layer.insertSublayer(previewLayer, at: 0)
}
- 遵守協(xié)議腊徙,實(shí)現(xiàn)代理方法
extension ViewController : AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate {
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
if connection == videoConnect {
print("視頻數(shù)據(jù)")
} else {
print("音頻數(shù)據(jù)")
}
}
}
停止掃描:
- 比如用戶不再直接简十,我們需要停止掃描
- 移除預(yù)覽圖層(不再直播肯定不需要預(yù)覽圖層了)
- 停止掃描(調(diào)用session的stopRunning方法)
- 將session設(shè)置為nil(對(duì)象不再使用,指針置空)
@IBAction func stopScanning() {
// 1.移除圖層
previewLayer?.removeFromSuperlayer()
// 2.停止掃描
session?.stopRunning()
// 3.將對(duì)象重置為nil
session = nil
}
切換鏡頭&聚焦&寫入文件
切換鏡頭(前置&后置攝像頭)
-
切換步驟
- 給切換過程添加動(dòng)畫
- 獲取當(dāng)前攝像頭是前置還是后置
- 取出相反的攝像頭(之前是前置撬腾,這次取出后置)
- 通過新攝像頭重新獲取設(shè)備(AVCaptureDevice)
- 通過設(shè)備(AVCaptureDevice)創(chuàng)建新的輸入(AVCaptureDeviceInput)
- 移除舊input&添加新的input
- 注意:修改session配置之前先調(diào)用開啟修改配置選項(xiàng)螟蝙,配置完成后,調(diào)用提交修改配置選項(xiàng)
- session?.beginConfiguration()
- session?.commitConfiguration()
- 保存新的input
圖例解析:
- 代碼如下:
@IBAction func switchScene() {
// 0.執(zhí)行動(dòng)畫
let rotaionAnim = CATransition()
rotaionAnim.type = "oglFlip"
rotaionAnim.subtype = "fromLeft"
rotaionAnim.duration = 0.5
view.layer.add(rotaionAnim, forKey: nil)
// 1.校驗(yàn)videoInput是否有值
guard let videoInput = videoInput else { return }
// 2.獲取當(dāng)前鏡頭
let position : AVCaptureDevicePosition = videoInput.device.position == .front ? .back : .front
// 3.創(chuàng)建新的input
guard let devices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as? [AVCaptureDevice] else { return }
guard let newDevice = devices.filter({$0.position == position}).first else { return }
guard let newVideoInput = try? AVCaptureDeviceInput(device: newDevice) else { return }
// 4.移除舊輸入民傻,添加新輸入
session?.beginConfiguration()
session?.removeInput(videoInput)
session?.addInput(newVideoInput)
session?.commitConfiguration()
// 5.保存新輸入
self.videoInput = newVideoInput
}
寫入文件
寫入文件步驟:
- 創(chuàng)建AVCaptureMovieFileOutput對(duì)象
- 用于將音頻視頻寫入文件
- 將movieFileOutput對(duì)象胰默,添加到session的輸出中
- 寫入文件也是一種輸出
- 設(shè)置視頻的穩(wěn)定模式
- 不設(shè)置可能會(huì)出現(xiàn)視頻跳幀等問題
- 通常設(shè)置為自動(dòng)即可
- 開始寫入
- 錄制完成,停止寫入即可
代碼解析:
代碼如下:
- 創(chuàng)建漓踢、添加牵署、設(shè)置代碼
// 添加文件輸出
let movieFileoutput = AVCaptureMovieFileOutput()
self.movieFileOutput = movieFileoutput
session.addOutput(movieFileoutput)
// 獲取視頻的connection
let connection = movieFileoutput.connection(withMediaType: AVMediaTypeVideo)
// 設(shè)置視頻的穩(wěn)定模式
connection?.preferredVideoStabilizationMode = .auto
// 開始寫入視頻
movieFileoutput.startRecording(toOutputFileURL: outputFileURL, recordingDelegate: self)
- 停止寫入代碼
// 0.停止寫入
self.movieFileOutput?.stopRecording()
- 在代理方法中監(jiān)聽開始、結(jié)束事件
extension ViewController : AVCaptureFileOutputRecordingDelegate {
func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {
print("開始錄制")
}
func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {
print("停止錄制")
}
}