iOS直播技術(shù)學(xué)習(xí)筆記-采集視頻(二)

概述

  • 音視頻采集是直播架構(gòu)的第一環(huán)瞎抛,是視頻的來源

    • 其實(shí)視頻的采集有多個(gè)應(yīng)用場景:比如二維碼開發(fā)
  • 音視頻采集包括兩部分:

    • 視頻采集
    • 音頻采集
  • 在iOS開發(fā)中垂蜗,是可以同步采集視頻&音頻的庆械,使用方式也非常簡單

  • 相關(guān)的采集API都封裝在AVFoundation框架中,導(dǎo)入對應(yīng)框架跳昼,實(shí)現(xiàn)功能即可

采集步驟

采集步驟文字描述
  • PS:如果做過二維碼開發(fā)对嚼,應(yīng)該對相關(guā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方法即可開始采集
代碼解析
  • 整體代碼步驟
整體代碼.png
  • 函數(shù)一(設(shè)置視頻輸入輸出)
設(shè)置視頻輸入輸出.png
  • 函數(shù)二(設(shè)置音頻輸入輸出)
設(shè)置音頻輸入輸出.png
  • 添加預(yù)覽圖層
添加預(yù)覽圖層.png
  • 遵守協(xié)議媒区,實(shí)現(xiàn)代理方法
遵守協(xié)議,實(shí)現(xiàn)代理方法.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ù)覽圖層 
    // 添加預(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)前攝像頭是前置還是后置

    • 取出相反的攝像頭(之前是前置湾碎,這次取出后置)

    • 通過新攝像頭重新獲取設(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í)行動畫
        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è)置為自動即可
    • 開始寫入

    • 錄制完成递惋,停止寫入即可

  • 代碼如下:
  • 創(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("停止錄制")
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市形真,隨后出現(xiàn)的幾起案子杉编,更是在濱河造成了極大的恐慌,老刑警劉巖咆霜,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邓馒,死亡現(xiàn)場離奇詭異,居然都是意外死亡蛾坯,警方通過查閱死者的電腦和手機(jī)绒净,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偿衰,“玉大人挂疆,你說我怎么就攤上這事∠卖幔” “怎么了缤言?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長视事。 經(jīng)常有香客問我胆萧,道長,這世上最難降的妖魔是什么俐东? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任跌穗,我火速辦了婚禮,結(jié)果婚禮上虏辫,老公的妹妹穿的比我還像新娘蚌吸。我一直安慰自己,他們只是感情好砌庄,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布羹唠。 她就那樣靜靜地躺著,像睡著了一般娄昆。 火紅的嫁衣襯著肌膚如雪佩微。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天萌焰,我揣著相機(jī)與錄音哺眯,去河邊找鬼。 笑死扒俯,一個(gè)胖子當(dāng)著我的面吹牛奶卓,可吹牛的內(nèi)容都是我干的一疯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼寝杖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了互纯?” 一聲冷哼從身側(cè)響起瑟幕,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎留潦,沒想到半個(gè)月后只盹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兔院,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年殖卑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坊萝。...
    茶點(diǎn)故事閱讀 40,567評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡孵稽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出十偶,到底是詐尸還是另有隱情菩鲜,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布惦积,位于F島的核電站接校,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狮崩。R本人自食惡果不足惜蛛勉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望睦柴。 院中可真熱鬧诽凌,春花似錦、人聲如沸坦敌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恬试。三九已至窝趣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間训柴,已是汗流浹背哑舒。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幻馁,地道東北人洗鸵。 一個(gè)月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓越锈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親膘滨。 傳聞我的和親對象是個(gè)殘疾皇子甘凭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評論 2 359

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