概述

  • 音视频采集是直播架构的第一环,是视频的来源
    其实视频的采集有多个应用场景:比如二维码开发
  • 音视频采集包括两部分
    视频采集
    音频采集
  • 在iOS开发中,是可以同步采集视频&音频的,使用方式也非常简单
  • 相关的采集API都封装在AVFoundation框架中,导入对应框架,实现功能即可

采集步骤

相关API主要在AVFoundation框架中,因此需要先导入框架

创建捕捉会话(AVCaptureSession)

该会话用于连接之后的输入源&输出源
输入源:摄像头&话筒
输出源:拿到对应的音频&视频数据的出口
会话:用于将输入源&输出源连接起来
fileprivate lazy var session = AVCaptureSession()

设置视频输入源&输出源

输入源(AVCaptureDeviceInput):从摄像头输入
输出源(AVCaptureVideoDataOutput):可以设置代理,在代理方法中拿到数据
将输入&输出添加到会话中
// 给会话设置视频源(输入源&输出源)
fileprivate func setupVideoSource() {
        // 1.创建输入
        // 1.1.获取所有的设备(包括前置&后置摄像头)builtInTelephotoCamera,builtInDualCamera,builtInTrueDepthCamera
        guard let device = AVCaptureDevice.DiscoverySession.init(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .front).devices.first else { return }
        
        // 1.3.通过前置摄像头创建输入设备
        guard let videoInput = try? AVCaptureDeviceInput(device: device) else { return }
        self.vedioInput = videoInput
        // 2.创建输出源
        // 2.1.创建视频输出源
        let videoOutput = AVCaptureVideoDataOutput()
        
        // 2.2.设置代理,以及代理方法的执行队列(在代理方法中拿到采集到的数据)
        let queue = DispatchQueue.global()
        videoOutput.setSampleBufferDelegate(self, queue: queue)
        
        self.videoOutPut = videoOutput
        
        // 3.将输入&输出添加到会话中
        addToSession(videoInput, videoOutput)
    }
    
     // 添加到session
    fileprivate func addToSession(_ input: AVCaptureInput,_ output: AVCaptureOutput) {
        // 3.将输入&输出添加到会话中
        session.beginConfiguration()
        if session.canAddInput(input) {
            session.addInput(input)
        }
        if session.canAddOutput(output) {
            session.addOutput(output)
        }
        session.commitConfiguration()
    }

设置音频输入源&输出源

输入源(AVCaptureDeviceInput):从话筒输入
输出源(AVCaptureAudioDataOutput):可以设置代理,在代理方法中拿到数据
将输入&输出添加到会话中
// 给会话设置音频源(输入源&输出源)
    fileprivate func setupAudioSource() {
        // 1.创建输入
        guard let device = AVCaptureDevice.DiscoverySession.init(deviceTypes: [.builtInMicrophone], mediaType: .audio, position: .unspecified).devices.first else { return }
        guard let audioInput = try? AVCaptureDeviceInput(device: device) else { return }
        
        // 2.创建输出源
        let audioOutput = AVCaptureAudioDataOutput()
        let queue = DispatchQueue.global()
        audioOutput.setSampleBufferDelegate(self, queue: queue)
        
        addToSession(audioInput, audioOutput)
    }

添加预览图层(可选)

如果希望用户看到采集的画面,可以添加预览图层
该预览图层不是必须的,及时没有添加也可以正常采集数据
// 添加预览图层
    fileprivate func setupPreviewLayer() {
        // 1.创建预览图层
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        
        // 2.设置图层的属性
        previewLayer.frame = view.bounds
        
        // 3.将图层添加到view中
        view.layer.insertSublayer(previewLayer, at: 0)
        self.previewLayer = previewLayer
    }

开始采集即可
调用会话(AVCaptureSession)的startRunning方法即可开始采集

// 开始采集
 session.startRunning()

停止扫描

比如用户不再直接,我们需要停止扫描
移除预览图层(不再直播肯定不需要预览图层了)
停止扫描(调用session的stopRunning方法)
将session设置为nil(对象不再使用,指针置空)
// 停止采集
session.stopRunning()

切换镜头(前置&后置摄像头)

切换步骤

给切换过程添加动画
获取当前摄像头是前置还是后置
取出相反的摄像头(之前是前置,这次取出后置)
通过新摄像头重新获取设备(AVCaptureDevice)
通过设备(AVCaptureDevice)创建新的输入(AVCaptureDeviceInput)
移除旧input&添加新的input
注意:修改session配置之前先调用开启修改配置选项,配置完成后,调用提交修改配置选项
session?.beginConfiguration()
session?.commitConfiguration()
保存新的input
/// 切换摄像头
    @IBAction func rotateCamera(_ sender: UIButton) {
        
        // 0.执行动画
        let rotaionAnim = CATransition()
        rotaionAnim.type =  .fade
        rotaionAnim.subtype = .fromLeft
        rotaionAnim.duration = 0.5
        view.layer.add(rotaionAnim, forKey: nil)
        
        
        // 拿到之前的设备
        guard let vedioInput = vedioInput else {
            return
        }
        // 计算现在要切换的设备(如果之前是前置现在就是后置)
        let position: AVCaptureDevice.Position = vedioInput.device.position == .front ? .back : .front
        // 获取对象的输入对象
        guard let device = AVCaptureDevice.DiscoverySession.init(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position).devices.first else { return }
        
        // 通过设备创建输入设备
        guard let newVideoInput = try? AVCaptureDeviceInput(device: device) else { return }
        // 记录当前的设备
        self.vedioInput = newVideoInput
        // 移除之前的  添加新的
        session.beginConfiguration()
        session.removeInput(vedioInput )
        if session.canAddInput(newVideoInput) {
            session.addInput(newVideoInput)
        }
        session.commitConfiguration()
    }

写入文件

**写入文件步骤**
创建AVCaptureMovieFileOutput对象
用于将音频视频写入文件
将movieFileOutput对象,添加到session的输出中
写入文件也是一种输出
设置视频的稳定模式
不设置可能会出现视频跳帧等问题
通常设置为自动即可
开始写入
录制完成,停止写入即可
/// 存储音视频到文件
    fileprivate func setupMovieOutputFile() {
        // 获取文件的输出对象
        let fileOutput = AVCaptureMovieFileOutput()
        self.movieFileOutput = fileOutput
        // 创建文件路径
        let filePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/app.mp4"
        
        let fileUrl = URL(fileURLWithPath: filePath)
        // 获取视频的连接
        let connection = fileOutput.connection(with: .video)
        // 设置视频的稳定模式
        connection?.automaticallyAdjustsVideoMirroring = true
        
        session.beginConfiguration()
        if session.canAddOutput(fileOutput) {
            session.addOutput(fileOutput)
        }
        session.commitConfiguration()
        
        fileOutput.startRecording(to: fileUrl, recordingDelegate: self)
    }

停止写入代码

// 0.停止写入
        self.movieFileOutput?.stopRecording()

在代理方法中监听开始、结束事件

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地址