使用 AVFoundation 進(jìn)行簡單的視頻錄制

這里的視頻錄制,錄制的視頻只是直接存放到文件中

畫大餅

使用 AVFoundation 可以說是挺有模塊化思想的:

  1. 創(chuàng)建一個視頻輸入(AVCaptureDevice + AVCaptureDeviceInput)
  2. 創(chuàng)建一個音頻輸入(AVCaptureDevice + AVCaptureDeviceInput)
  3. 創(chuàng)建一個音頻視頻的輸出(AVCaptureMovieFileOutput)
  4. 將視頻輸入和音頻輸入,音頻視頻輸出添加到一個叫 Session 的東西里面進(jìn)行統(tǒng)一的管理(AVCaptureSession)

因此,數(shù)據(jù)的流動方向?qū)⑹牵?/p>

(圖像, 聲音) -> AVCaptureDevice -> AVCaptureDeviceInput -> AVCaptureSession -> AVCaptureMovieFileOuput -> (文件)

配置視頻輸入

func setupCameraInput() throws -> AVCaptureDeviceInput {
        
   let cameraDevice = AVCaptureDevice.default(for: .video)
   
   do {
       let cameraInput = try AVCaptureDeviceInput(device: cameraDevice!)
       return cameraInput
   } catch {
       throw error
   }
}

以上配置使用了視頻輸入的默認(rèn)設(shè)置

通過 AVCaptureDevice.default(for: .video) 獲得硬件的訪問對象,再通過這個對象創(chuàng)建一個軟件上的對象

配置音頻輸入

func setupMicInput() throws -> AVCaptureDeviceInput {
   let micDevice = AVCaptureDevice.default(for: .audio)
   
   do {
       let audioInput = try AVCaptureDeviceInput(device: micDevice!)
       return audioInput
   } catch {
       throw error
   }
}

以上配置使用了音頻輸入的默認(rèn)設(shè)置

通過 AVCaptureDevice.default(for: .audio) 獲得硬件的訪問對象,再通過這個對象創(chuàng)建一個軟件上的對象

配置 Session

func setupCaptureSession() throws -> AVCaptureSession {
        
   do {
       let cameraInput = try setupCameraInput()
       let micInput = try setupMicInput()
       
       let captureSession = AVCaptureSession()
       if captureSession.canAddInput(cameraInput) {
           captureSession.addInput(cameraInput)
       }
       
       if captureSession.canAddInput(micInput) {
           captureSession.addInput(micInput)
       }
       
       return captureSession
   } catch {
       throw error
   }
}

以上過程,將視頻和音頻的輸入配置到了一個 Session 中

需要注意的是,對 session 的調(diào)用都是阻塞式的,因此,調(diào)用有關(guān) session API 的過程,應(yīng)該在一個后臺的串行隊列中進(jìn)行,即,以上的配置過程,應(yīng)該在一個后臺的串行隊列中進(jìn)行

配置輸出

由于配置視頻和音頻時,有可能會拋出異常,因此,這里會在當(dāng)視頻和音頻成功配置后,才會再進(jìn)行輸出的配置

self.fileOutput = AVCaptureMovieFileOutput()
if self.captureSession.canAddOutput(self.fileOutput!) {
    self.captureSession.addOutput(self.fileOutput!)
}

配置視頻的文件輸出比較簡單,只需要創(chuàng)建一個對象,并添加到 session 中即可

寫一個總的配置過程

配置輸入

func prepareCapture(completion: @escaping (Bool, AVCaptureSession?, Error?) -> Void) {
   serialQ.async { [unowned self] in
       do {
           let captureSession = try self.setupCaptureSession()
           completion(true, captureSession, nil)
       } catch {
           completion(false, nil, error)
       }
   }
}

serialQ 是一個后臺的串行隊列,用來進(jìn)行有關(guān) session 的操作,避免阻塞

let serialQ = DispatchQueue(label: "com.wenyongyang.createCaptureSession")


completion 是一個異步回調(diào),在配置輸入完成后進(jìn)行回調(diào),無論成功或失敗

配置輸出和預(yù)覽界面

prepareCapture { [unowned self] (success, session, error) in
  if success {
      self.captureSession = session!
      self.fileOutput = AVCaptureMovieFileOutput()
      if self.captureSession.canAddOutput(self.fileOutput!) {
          self.captureSession.addOutput(self.fileOutput!)
      }
      self.captureSession.startRunning()
      
      let previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
      DispatchQueue.main.async {
          previewLayer.frame = self.view.bounds
          self.view.layer.insertSublayer(previewLayer, below: self.btnStart.layer)
      }
  } else {
      print("\(error!.localizedDescription)")
  }
}

回調(diào)參數(shù) (success, session, error)

success 指示輸入配置是否成功,true 為成功,否則 false

session 為 optional,若輸入配置成功,則是一個 AVCaptureSession 對象,否則為 nil

error 為 optional,若輸入配置成功,則為 nil,否則會是配置輸入時拋出的異常


若輸入配置成功,將會接著配置輸出

并且,將會創(chuàng)建一個用于預(yù)覽的 layer

let previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
DispatchQueue.main.async {
    previewLayer.frame = self.view.bounds
    self.view.layer.insertSublayer(previewLayer, below: self.btnStart.layer)
}

self.captureSession.startRunning() 這一句,并不是真正的錄制開始,而只是開始捕捉畫面并顯示在預(yù)覽層上

開始錄制

serialQ.async { [unowned self] in
  let saveURL = self.savePath()
  if !self.captureSession.isRunning { self.captureSession.startRunning() }
  self.fileOutput?.startRecording(to: saveURL, recordingDelegate: self)
}

要進(jìn)行真正的錄制,即將視頻存儲到了文件中,需要調(diào)用輸出對象(AVCaptureMovieFileOutput)的方法,同時還需要指定存儲的路徑


recordingDelegate 的協(xié)議是 AVCaptureFileOutputRecordingDelegate

在這個協(xié)議中,我們可以獲悉到錄制的開始與結(jié)束的時機(jī),如

/// 錄制開始
func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) {
   print("start")
}

/// 錄制結(jié)束
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
   print("\(outputFileURL)")
   print("finish")
}

停止錄制

serialQ.async { [unowned self] in
  self.captureSession.stopRunning()
  self.fileOutput?.stopRecording()
}

注意事項

  • 攝像頭的隱私訪問描述字段 -> NSCameraUsageDescription
  • 麥克風(fēng)的隱私訪問描述字段 -> NSMicrophoneUsageDescription
  • 當(dāng)需要改變 session 的配置時,如改變錄制質(zhì)量,切換前后攝像頭等操作時,需要調(diào)用 lockForConfiguration() 來獨占訪問權(quán)限,并使用 unlockForConfiguration() 進(jìn)行釋放權(quán)限
  • 當(dāng)用戶未授權(quán)時,對于錄制視頻或音頻的嘗試,得到的將是黑色畫面和無聲

References

在 iOS 上捕獲視頻

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

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

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