iOS - <直播系列> 視頻采集

概述

  • 音視頻采集是直播架構(gòu)的第一環(huán),是視頻的來源
    • 其實(shí)視頻的采集有多個(gè)應(yīng)用場景:比如二維碼開發(fā)
  • 音視頻采集包括兩部分:
    • 視頻采集
    • 音頻采集
  • 在iOS開發(fā)中,是可以同步采集視頻&音頻的,使用方式也非常簡單
  • 相關(guān)的采集API都封裝在AVFoundation框架中,導(dǎo)入對應(yīng)框架,實(shí)現(xiàn)功能即可
采集步驟:
  • 采集步驟文字描述

  • 導(dǎo)入框架

    • 相關(guān)API主要在AVFoundation框架中,因此需要先導(dǎo)入框架
  • 創(chuàng)建捕捉會話(AVCaptureSession)

    • 該會話用于連接之后的輸入源&輸出源
    • 輸入源:攝像頭&話筒
    • 輸出源:拿到對應(yīng)的音頻&視頻數(shù)據(jù)的出口
    • 會話:用于將輸入源&輸出源連接起來
  • 設(shè)置視頻輸入源&輸出源

    • 輸入源(AVCaptureDeviceInput):從攝像頭輸入
    • 輸出源(AVCaptureVideoDataOutput):可以設(shè)置代理,在代理方法中拿到數(shù)據(jù)
    • 將輸入&輸出添加到會話中
  • 設(shè)置音頻輸入源&輸出源

    • 輸入源(AVCaptureDeviceInput):從話筒輸入
    • 輸出源(AVCaptureAudioDataOutput):可以設(shè)置代理,在代理方法中拿到數(shù)據(jù)
    • 將輸入&輸出添加到會話中
  • 添加預(yù)覽圖層(可選)

    • 如果希望用戶看到采集的畫面,可以添加預(yù)覽圖層
    • 該預(yù)覽圖層不是必須的,及時(shí)沒有添加也可以正常采集數(shù)據(jù)
  • 開始采集即可

    • 調(diào)用會話(AVCaptureSession)的startRunning方法即可開始采集
      代碼解析
整體代碼步驟:
Snip20161011_38.png
  • 函數(shù)一(設(shè)置視頻輸入輸出)
![Uploading Snip20161011_41_095517.png . . .]
  • 函數(shù)二(設(shè)置音頻輸入輸出)
Snip20161011_41.png
  • 添加預(yù)覽圖層
Snip20161011_42.png
  • 遵守協(xié)議,實(shí)現(xiàn)代理方法
Snip20161011_44.png
實(shí)現(xiàn)代碼:
  • 整體步驟代碼
    // 1.創(chuàng)建捕捉會話
    let session = AVCaptureSession()

    // 2.設(shè)置視頻輸入輸出
    setupVideoSource(session: session)

    // 3.設(shè)置音頻輸入輸出
    setupAudioSource(session: session)

    // 4.添加預(yù)覽圖層
    setupPreviewLayer(session: session)

    // 5.開始掃描
    session.startRunning()
  • 函數(shù)一(設(shè)置視頻輸入輸出)
    // 給會話設(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.將輸入&輸出添加到會話中
        // 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è)置音頻輸入輸出)
    // 給會話設(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.將輸入&輸出添加到會話中
        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(對象不再使用,指針置空)
    @IBAction func stopScanning() {
        // 1.移除圖層
        previewLayer?.removeFromSuperlayer()

        // 2.停止掃描
        session?.stopRunning()

        // 3.將對象重置為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
  • 圖例解析:

Snip20161012_45.png
  • 代碼如下:
    @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對象
    • 用于將音頻視頻寫入文件
  • 將movieFileOutput對象,添加到session的輸出中
    • 寫入文件也是一種輸出
  • 設(shè)置視頻的穩(wěn)定模式
    • 不設(shè)置可能會出現(xiàn)視頻跳幀等問題
    • 通常設(shè)置為自動(dòng)即可
  • 開始寫入
  • 錄制完成,停止寫入即可
代碼解析:
Snip20161015_52.png
代碼如下:
  • 創(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("停止錄制")
    }
}

Demo下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容