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

概述

  • 音視頻采集是直播架構(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方法即可開始采集
      代碼解析
整體代碼步驟:
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)建捕捉會(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
  • 圖例解析:

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對(duì)象
    • 用于將音頻視頻寫入文件
  • 將movieFileOutput對(duì)象胰默,添加到session的輸出中
    • 寫入文件也是一種輸出
  • 設(shè)置視頻的穩(wěn)定模式
    • 不設(shè)置可能會(huì)出現(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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喧半,一起剝皮案震驚了整個(gè)濱河市奴迅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌薯酝,老刑警劉巖半沽,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吴菠,居然都是意外死亡者填,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門做葵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來占哟,“玉大人,你說我怎么就攤上這事酿矢≌ズ酰” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵瘫筐,是天一觀的道長蜜暑。 經(jīng)常有香客問我,道長策肝,這世上最難降的妖魔是什么肛捍? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮之众,結(jié)果婚禮上拙毫,老公的妹妹穿的比我還像新娘。我一直安慰自己棺禾,他們只是感情好缀蹄,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般缺前。 火紅的嫁衣襯著肌膚如雪蛀醉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天诡延,我揣著相機(jī)與錄音滞欠,去河邊找鬼。 笑死肆良,一個(gè)胖子當(dāng)著我的面吹牛筛璧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惹恃,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼夭谤,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了巫糙?” 一聲冷哼從身側(cè)響起朗儒,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎参淹,沒想到半個(gè)月后醉锄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浙值,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年恳不,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片开呐。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烟勋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筐付,到底是詐尸還是另有隱情卵惦,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布瓦戚,位于F島的核電站沮尿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏较解。R本人自食惡果不足惜畜疾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哨坪。 院中可真熱鬧庸疾,春花似錦乍楚、人聲如沸当编。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忿偷。三九已至金顿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲤桥,已是汗流浹背揍拆。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茶凳,地道東北人嫂拴。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像贮喧,于是被迫代替她去往敵國和親筒狠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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