使用 AVFoundation 进行简单的视频录制

2017-06-18  本文已影响60人  扬扬扬

这里的视频录制,录制的视频只是直接存放到文件中

画大饼

使用 AVFoundation 可以说是挺有模块化思想的:

  1. 创建一个视频输入(AVCaptureDevice + AVCaptureDeviceInput)
  2. 创建一个音频输入(AVCaptureDevice + AVCaptureDeviceInput)
  3. 创建一个音频视频的输出(AVCaptureMovieFileOutput)
  4. 将视频输入和音频输入,音频视频输出添加到一个叫 Session 的东西里面进行统一的管理(AVCaptureSession)

因此,数据的流动方向将是:

(图像, 声音) -> 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
   }
}

以上配置使用了视频输入的默认设置

通过 AVCaptureDevice.default(for: .video) 获得硬件的访问对象,再通过这个对象创建一个软件上的对象

配置音频输入

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

以上配置使用了音频输入的默认设置

通过 AVCaptureDevice.default(for: .audio) 获得硬件的访问对象,再通过这个对象创建一个软件上的对象

配置 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 的调用都是阻塞式的,因此,调用有关 session API 的过程,应该在一个后台的串行队列中进行,即,以上的配置过程,应该在一个后台的串行队列中进行

配置输出

由于配置视频和音频时,有可能会抛出异常,因此,这里会在当视频和音频成功配置后,才会再进行输出的配置

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

配置视频的文件输出比较简单,只需要创建一个对象,并添加到 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 是一个后台的串行队列,用来进行有关 session 的操作,避免阻塞

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


completion 是一个异步回调,在配置输入完成后进行回调,无论成功或失败

配置输出和预览界面

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)")
  }
}

回调参数 (success, session, error)

success 指示输入配置是否成功,true 为成功,否则 false

session 为 optional,若输入配置成功,则是一个 AVCaptureSession 对象,否则为 nil

error 为 optional,若输入配置成功,则为 nil,否则会是配置输入时抛出的异常


若输入配置成功,将会接着配置输出

并且,将会创建一个用于预览的 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() 这一句,并不是真正的录制开始,而只是开始捕捉画面并显示在预览层上

开始录制

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

要进行真正的录制,即将视频存储到了文件中,需要调用输出对象(AVCaptureMovieFileOutput)的方法,同时还需要指定存储的路径


recordingDelegate 的协议是 AVCaptureFileOutputRecordingDelegate

在这个协议中,我们可以获悉到录制的开始与结束的时机,如

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

/// 录制结束
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()
}

注意事项

References

在 iOS 上捕获视频

上一篇下一篇

猜你喜欢

热点阅读