iOS 視頻捕獲系列Swift之AVFoundation(一)

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

  • AVFoundationUIImagePickerController 的區(qū)別在于 在于對視頻流的處理备籽,顯然前者會更高級一點(diǎn)舶治。
  • AVFoundation中對視頻的輸出處理 又分為 AVCaptureMovieFileOutputAVAssetWriter 。這里如果想要對視頻的輸出給出更多的操作那選擇 AVAssetWriter 是個不錯的選擇车猬。
  • 所以這里重點(diǎn)介紹 AVFoundation 更多的區(qū)別霉猛,還是在代碼中體驗(yàn)比較好,說太多都沒用珠闰。就是
為了更好的查看其關(guān)系惜浅,下面的圖比較直觀一點(diǎn):
t
t

首先我們新建一個工程

并在工程中的 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)擊事件

由上圖我們可以看到輸出方式有兩種 AVCaptureMovieFileOutputAVAssetWriter ,在輸出之前他們對視頻的操作是一樣的伏嗜,所以我們可以把 它倆公共的部分抽象出來一個類坛悉,對使用不同的輸出方式進(jìn)行繼承這個類就 OK

相同的部分抽象成 一個繼承 NSObjectCaptureSessionCoordinator 公共類
該公共類不對采集后的視頻不做輸出處理,因?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輸出有兩種:AVCaptureMovieFileOutputAVAssetWriter浊猾。
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)啦!
如圖:

如圖
如圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末偷办,一起剝皮案震驚了整個濱河市艰额,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌椒涯,老刑警劉巖柄沮,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異废岂,居然都是意外死亡祖搓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門湖苞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拯欧,“玉大人,你說我怎么就攤上這事财骨「渥鳎” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵隆箩,是天一觀的道長该贾。 經(jīng)常有香客問我,道長捌臊,這世上最難降的妖魔是什么杨蛋? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮娃属,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘护姆。我一直安慰自己矾端,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布卵皂。 她就那樣靜靜地躺著秩铆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上殴玛,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天捅膘,我揣著相機(jī)與錄音,去河邊找鬼滚粟。 笑死寻仗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凡壤。 我是一名探鬼主播署尤,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼亚侠!你這毒婦竟也來了曹体?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤硝烂,失蹤者是張志新(化名)和其女友劉穎箕别,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滞谢,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡串稀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爹凹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厨诸。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖禾酱,靈堂內(nèi)的尸體忽然破棺而出微酬,到底是詐尸還是另有隱情,我是刑警寧澤颤陶,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布颗管,位于F島的核電站,受9級特大地震影響滓走,放射性物質(zhì)發(fā)生泄漏垦江。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一搅方、第九天 我趴在偏房一處隱蔽的房頂上張望比吭。 院中可真熱鬧,春花似錦姨涡、人聲如沸衩藤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赏表。三九已至检诗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓢剿,已是汗流浹背逢慌。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留间狂,地道東北人攻泼。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像前标,于是被迫代替她去往敵國和親坠韩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容