美文网首页
使用 AVFoundation 进行简单的视频录制

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

作者: 扬扬扬 | 来源:发表于2017-06-18 14:22 被阅读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()
    }
    

    注意事项

    • 摄像头的隐私访问描述字段 -> NSCameraUsageDescription
    • 麦克风的隐私访问描述字段 -> NSMicrophoneUsageDescription
    • 当需要改变 session 的配置时,如改变录制质量,切换前后摄像头等操作时,需要调用 lockForConfiguration() 来独占访问权限,并使用 unlockForConfiguration() 进行释放权限
    • 当用户未授权时,对于录制视频或音频的尝试,得到的将是黑色画面和无声

    References

    在 iOS 上捕获视频

    相关文章

      网友评论

          本文标题:使用 AVFoundation 进行简单的视频录制

          本文链接:https://www.haomeiwen.com/subject/oiiiqxtx.html