iOS 視頻捕獲系列之AVFoundation(一)
AVCaptureMovieFileOutput系列
在iOS開發(fā)過程中,或多或少的都涉及視頻的操作劲厌。
尤其在去年直播行業(yè)的帶動下,移動端對視頻的處理也愈來愈發(fā)要求嚴(yán)格浪汪。
本文也是在 這篇 中參考而來悼院。
Swift
版本哦!
- 本文 demo 均可在 GitHub 獲得
- 本篇文章主要是讓你學(xué)會以
AVCaptureMovieFileOutput
式的輸出 - 歡迎關(guān)注個人 博客
blog.aiyinyu.com
本篇不涉及 :
視頻輸出質(zhì)量
幀率設(shè)置
具體的設(shè)備格式
像素格式
光學(xué)防抖
...等等
這些都會在下一篇
中帶你去認(rèn)識催首。如果還不會用,就想了解這么多泄鹏。就如同還不會走就要跑一樣郎任,是要跌大跟頭滴!
正文:
在iOS當(dāng)中對視頻捕捉一般是 :
UIImagePickerController
- 或者
AVFoundation
本文主要內(nèi)容是: AVFoundation
-
AVFoundation
與UIImagePickerController
的區(qū)別在于 在于對視頻流的處理备籽,顯然前者會更高級一點(diǎn)舶治。 - 而
AVFoundation
中對視頻的輸出處理 又分為AVCaptureMovieFileOutput
與AVAssetWriter
。這里如果想要對視頻的輸出給出更多的操作那選擇AVAssetWriter
是個不錯的選擇车猬。 - 所以這里重點(diǎn)介紹
AVFoundation
更多的區(qū)別霉猛,還是在代碼中體驗(yàn)比較好,說太多都沒用珠闰。就是干
為了更好的查看其關(guān)系惜浅,下面的圖比較直觀一點(diǎn):
首先我們新建一個工程
并在工程中的 plist
文件中添加訪問 權(quán)限
Privacy - Camera Usage Description
Privacy - Microphone Usage Description
Privacy - Photo Library Usage Description
Privacy - Media Library Usage Description
先來研究
AVCaptureMovieFileOutput
關(guān)于 AVCaptureMovieFileOutput
看上圖對號入座
首先新建一個 fileOutputViewController
控制器
控制器上放倆按鈕: Close
Record
你可以用 storyboard
拖拽也可以用代碼實(shí)現(xiàn)其點(diǎn)擊事件
由上圖我們可以看到輸出方式有兩種 AVCaptureMovieFileOutput
與 AVAssetWriter
,在輸出之前他們對視頻的操作是一樣的伏嗜,所以我們可以把 它倆公共的部分抽象出來一個類坛悉,對使用不同的輸出方式進(jìn)行繼承這個類就 OK
了
相同的部分抽象成 一個繼承 NSObject
的 CaptureSessionCoordinator
公共類
該公共類不對采集后的視頻不做輸出處理,因?yàn)檩敵鲇袃煞N不同的處理結(jié)果承绸。
每一種處理正是其繼承 CaptureSessionCoordinator
類的 子類
完成其處理
公共類:CaptureSessionCoordinator
對 AVCaptureSession
類進(jìn)行處理裸影,相關(guān)屬性如下:
AVCaptureSession
AVCaptureDevice
代理
視圖
如下:
因?yàn)槭褂玫骄€程,故對資源的加鎖問題军熏,在 Swift
中沒法直接向 Oc
那樣直接使用: synchronized
故在此利用閉包的特性達(dá)到相同的效果:
如何使用看文中代碼
func synchronized(_ lock: AnyObject,dispose: ()->()) {
objc_sync_enter(lock)
dispose()
objc_sync_exit(lock)
}
由于對視頻的處理都不是在主控制器fileOutputViewController
里面執(zhí)行的轩猩。故,對視頻的輸出都是需要代理來回調(diào)到控制器里面執(zhí)行后續(xù)的相關(guān)操作。
所以這里需要一個代理:
protocol captureSessionCoordinatorDelegate: class {
func coordinatorDidBeginRecording(coordinator: CaptureSessionCoordinator)
func didFinishRecording(coordinator: CaptureSessionCoordinator)
}
上面的鋪墊后,下面開始對 AVCaptureSession
進(jìn)行相應(yīng)的操作:
以我們的常識界轩,該類中必須有這些方法:
開始運(yùn)行
startRunning
結(jié)束運(yùn)行
stopRunning
開始記錄
startRecording
結(jié)束記錄
stopRecording
初始化初始化
AVCaptureVideoPreviewLayer
其他的方法可以在初始中進(jìn)行画饥,也可以進(jìn)行模塊化拆分
該類一個完整的代碼如下:
class CaptureSessionCoordinator: NSObject {
var captureSession: AVCaptureSession?
var cameraDevice: AVCaptureDevice?
var delegateCallQueue: DispatchQueue?
weak var delegate: captureSessionCoordinatorDelegate?
private var sessionQueue = DispatchQueue(label: "coordinator.Session")
private var previewLayer: AVCaptureVideoPreviewLayer?
override init() {
super.init()
captureSession = setupCaptureSession()
}
public func setDelegate(capDelegate: captureSessionCoordinatorDelegate,queue: DispatchQueue) {
synchronized(self) {
delegate = capDelegate
if delegateCallQueue != queue {
delegateCallQueue = queue
}
}
}
//MARK: ________________Session Setup________________
private func setupCaptureSession() -> AVCaptureSession {
let session = AVCaptureSession()
if !addDefaultCameraInputToCaptureSession(capSession: session) {
printLogDebug("failed to add camera input to capture session")
}
if addDefaultMicInputToCaptureSession(capSession: session) {
printLogDebug("failed to add mic input to capture session")
}
return session
}
private func addDefaultCameraInputToCaptureSession(capSession: AVCaptureSession) -> Bool {
do {
let cameraInput = try AVCaptureDeviceInput(device: AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo))
let success = addInput(input: cameraInput, capSession: capSession)
cameraDevice = cameraInput.device
return success
} catch let error as NSError {
printLogDebug("error configuring camera input: \(error.localizedDescription)")
return false
}
}
private func addDefaultMicInputToCaptureSession(capSession: AVCaptureSession) -> Bool {
do {
let micInput = try AVCaptureDeviceInput(device: AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio))
let success = addInput(input: micInput, capSession: capSession)
return success
} catch let error as NSError {
printLogDebug("error configuring mic input: \(error.localizedDescription)")
return false
}
}
//MARK: ________________Public Api________________
func addInput(input: AVCaptureDeviceInput,capSession: AVCaptureSession) -> Bool {
if capSession.canAddInput(input) {
capSession.addInput(input)
return true
}
printLogDebug("input error")
return false
}
func addOutput(output: AVCaptureOutput,capSession: AVCaptureSession) -> Bool {
if capSession.canAddOutput(output) {
capSession.addOutput(output)
return true
}
printLogDebug("output error")
return false
}
func startRunning() {
sessionQueue.async {
self.captureSession?.startRunning()
}
}
func stopRunning() {
sessionQueue.async {
self.stopRunning()
self.captureSession?.stopRunning()
}
}
func startRecording() {
// 子類繼承后重寫
}
func stopRecording() {
// 子類繼承后重寫
}
}
我們先來: AVCaptureMovieFileOutput
我們創(chuàng)建以 AVCaptureMovieFileOutput 方式輸出并繼承 CaptureSessionCoordinator 的類:fileOutputCoordinator
由最上面的大圖可知,AVFoundation
輸出有兩種:AVCaptureMovieFileOutput
與AVAssetWriter
浊猾。
而 AVCaptureMovieFileOutput
是對輸出流沒有做太多的處理,以AVCaptureMovieFileOutput
方式進(jìn)行視頻輸出處理的類,不需要太多的處理抖甘。
故繼承 CaptureSessionCoordinator
它的fileOutputCoordinator
子類只需如下:
重點(diǎn)便是對輸出的處理
class fileOutputCoordinator: CaptureSessionCoordinator,AVCaptureFileOutputRecordingDelegate {
var movieFileOutput: AVCaptureMovieFileOutput?
override init() {
super.init()
movieFileOutput = AVCaptureMovieFileOutput()
_ = addOutput(output: movieFileOutput!, capSession: captureSession!)
}
override func startRecording() {
let fm = YfileManager()
let tempUrl = fm.tempFileUrl()
movieFileOutput?.startRecording(toOutputFileURL: tempUrl, recordingDelegate: self)
}
override func stopRecording() {
movieFileOutput?.stopRecording()
}
func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {
delegate?.didFinishRecording(coordinator: self, url: outputFileURL)
}
func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {
delegate?.coordinatorDidBeginRecording(coordinator: self)
}
}
上面代碼中有一個對文件處理的路徑操作類:YfileManager
它主要就是對文件路徑的操作,與臨時(shí)文件存儲到系統(tǒng)相冊中的操作:以上代碼中牽扯到的只有如下:
class YfileManager: NSObject {
func tempFileUrl() -> URL {
var path: String = ""
let fm = FileManager.default
var i: Int = 0
while path.isEmpty || fm.fileExists(atPath: path) {
path = NSTemporaryDirectory() + "output\(i.description).mov"
i += 1
}
return URL(fileURLWithPath: path)
}
/// 對臨時(shí)視頻文件的存儲操作,本方法在iOS9以后被遺棄了
func copFileToCameraRoll(fileUrl: URL) {
let library = ALAssetsLibrary()
if !library.videoAtPathIs(compatibleWithSavedPhotosAlbum: fileUrl) {
printLogDebug("video error")
}
library.writeVideoAtPath(toSavedPhotosAlbum: fileUrl) { (url, error) in
if (error != nil) {
printLogDebug("error: \(error?.localizedDescription)")
} else if url == nil {
printLogDebug("url is empty")
}
}
}
}
到目前為止涉及非控制器(fileOutputViewController)的代碼全部完成,接下來我們來到控制器執(zhí)行相關(guān)的操作
實(shí)現(xiàn)fileOutputViewController
控制器的方法
首當(dāng)其沖的是相機(jī)視圖與執(zhí)行代理的方法:captureSessionCoordinatorDelegate
相關(guān)變量:
@IBOutlet weak var recordButton: UIBarButtonItem!
var captureSessionCoordinator: fileOutputCoordinator?
var recording: Bool = false
var dismissing: Bool = false
控制器具體代碼:
class fileOutputViewController: UIViewController,captureSessionCoordinatorDelegate {
@IBOutlet weak var recordButton: UIBarButtonItem!
var captureSessionCoordinator: fileOutputCoordinator?
var recording: Bool = false
var dismissing: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
captureSessionCoordinator = fileOutputCoordinator()
captureSessionCoordinator?.setDelegate(capDelegate: self, queue: DispatchQueue(label: "fileOutputCoordinator"))
confiureCamper()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/// 關(guān)閉當(dāng)前視圖
@IBAction func closeCameral(_ sender: Any) {
if recording {
dismissing = true
} else {
stopPipelineAndDismiss()
}
}
/// 開始記錄 與停止記錄
@IBAction func recording(_ sender: Any) {
if recording {
captureSessionCoordinator?.stopRecording()
} else {
UIApplication.shared.isIdleTimerDisabled = true
}
recordButton.isEnabled = false
recordButton.title = "Stop"
captureSessionCoordinator?.startRecording()
recording = true
}
func confiureCamper() {
let cameraViewlayer = captureSessionCoordinator?.previewLayerSetting()
cameraViewlayer?.frame = view.bounds
view.layer.insertSublayer(cameraViewlayer!, at: 0)
captureSessionCoordinator?.startRunning()
}
func stopPipelineAndDismiss() {
captureSessionCoordinator?.stopRunning()
dismiss(animated: true, completion: nil)
dismissing = false
}
func coordinatorDidBeginRecording(coordinator: CaptureSessionCoordinator) {
recordButton.isEnabled = true
}
func didFinishRecording(coordinator: CaptureSessionCoordinator, url: URL) {
UIApplication.shared.isIdleTimerDisabled = false
recordButton.title = "Record"
recording = false
let fm = YfileManager()
fm.copFileToCameraRoll(fileUrl: url)
if dismissing {
stopPipelineAndDismiss()
}
}
}
目前為止:到目前為止一個完整的
AVCaptureMovieFileOutput
類型的輸出完成
如果你在真機(jī)設(shè)備調(diào)試過程中想查看臨時(shí)的 tem文件葫慎,或者沙河的文件衔彻。那就接著如下看:
Xcode上面的導(dǎo)航欄
->Window
->Devices
->點(diǎn)擊你的設(shè)備
->找到右下的installed Apps
->點(diǎn)擊你的要看的項(xiàng)目
->點(diǎn)擊+ -右邊圖標(biāo)
->Download
下載到桌面即可 然后選擇顯示包內(nèi)容
就可以看到當(dāng)前沙盒文件的狀態(tài)啦!
如圖: