AVFoundation框架解析看这里(2)- 媒体捕捉与视频拍摄

前言

AVFoundation框架是ios中很重要的框架,所有与视频音频相关的软硬件控制都在这个框架里面,接下来这几篇就主要对这个框架进行介绍和讲解。
便于读者查阅这个AVFoundation框架系列,在此提供目录直通车。
AVFoundation框架解析目录
AVFoundation框架解析目录
AVFoundation框架解析目录

本章导读

上一章节主要从整体上全览AVFoundation框架,本章主要以一个小的需求(以媒体捕捉以起点,拍摄、保存视频),打开AVFoundation的大门,带领我们欣赏这个框架带来的强大功能。

媒体捕捉流程

AVCapture捕捉.png

需求结合

了解整个AVCapture捕捉流程后,就需要结合我们具体的需求修改对应的细节,比如按照实际需要修改输入源和输出源。

几个简单的需求例子:

  • 扫一扫:通过捕捉媒体,获取扫描内容,识别二维码或条形码
  • 拍照:通过捕捉媒体,拍摄照片
  • 录像:通过捕捉媒体,录制视频

Demo以录像为需求,通过AVCapture捕捉到画面后,使用对应输出源的数据,实现我们的需求。

视频流处理原理图.jpg

源码解析

就不用伪代码了,直接上源码。

func setCameraConfiguration() {
        //创建AVCaptureSession对象
        let captureSession = AVCaptureSession()
        self.captureSession = captureSession
        captureSession.sessionPreset = .high

        captureSession.beginConfiguration()
        
        //配置输入设备
        AVCaptureDevice.devices().forEach { (device) in
            if device.hasMediaType(.video) && device.position == .back {
                do {
                    let captureVideoDeviceInput = try AVCaptureDeviceInput(device: device)
                    if captureSession.canAddInput(captureVideoDeviceInput) {
                        captureSession.addInput(captureVideoDeviceInput)
                    }
                } catch {
                    
                }
            }
            
            if device.hasMediaType(.audio) {
                do {
                    let captureAudioDeviceInput = try AVCaptureDeviceInput(device: device)
                    if captureSession.canAddInput(captureAudioDeviceInput) {
                        captureSession.addInput(captureAudioDeviceInput)
                    }
                } catch {
                        
                }
            }
        }
        
        //配置输出设备
        let captureMovieFileOutput = AVCaptureMovieFileOutput()
        self.captureMovieFileOutput = captureMovieFileOutput
        if captureSession.canAddOutput(captureMovieFileOutput) {
            captureSession.addOutput(captureMovieFileOutput)
        }
        //设置链接管理对象
        if let captureConnection = captureMovieFileOutput.connection(with: .video) {
            captureConnection.videoScaleAndCropFactor = captureConnection.videoMaxScaleAndCropFactor  //视频旋转方向设置
            if captureConnection.isVideoStabilizationSupported {
                captureConnection.preferredVideoStabilizationMode = .auto
            }
        }
       
        captureSession.commitConfiguration()
        
        captureSession.startRunning()
    }

这里面有4大块,要吃的透彻才能活以致用。这四块本章不展开,如果有读者咨询的话会在后面详细讲。

  • AVCaptureSession
    AVCaptureSession:媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出。
    在视频捕获时,客户端可以实例化AVCaptureSession并添加适当的AVCaptureInputs、AVCaptureDeviceInput和输出,比如AVCaptureMovieFileOutput。通过[AVCaptureSession startRunning]开始数据流从输入到输出,和[AVCaptureSession stopRunning]停止输出输入的流动。客户端可以通过设置sessionPreset属性定制录制质量水平或输出的比特率。
  • AVCaptureInput与AVCaptureDevice
    设备输入数据管理对象,可以根据AVCaptureDevice创建对应AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。
  • AVCaptureOutput
    设备输出数据管理对象
  • AVCaptureVideoPreviewLayer
    相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的AVCaptureSession对象。

Demeo

import UIKit
import AVFoundation
import LEKit

class AVFCameraViewController: LEYViewController {
    //时长显示
    private var timeLabel: LEYLabel!
    
    private var timer: Timer? = nil
    private let timer_interval: Int = 1
    private var time_length: Int = 0
    
    
    private var captureSession: AVCaptureSession!
    private var  captureMovieFileOutput: AVCaptureMovieFileOutput!
    private var videoUrl: URL? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.naviView.title = "AVFoundation 视频拍摄"
        
        //获取权限后展示UI,判断照相机和麦克风权限
        RRXCAuthorizeManager.authorization(.camera) { (state) in
            if state == .authorized {
                RRXCAuthorizeManager.authorization(.microphone, completion: { (substate) in
                    if substate == .authorized {
                        self.setCameraConfiguration()
                        
                        self.drawUIDisplay()
                    }
                })
            }
        }
    }
    
    func drawUIDisplay() {
        self.naviView.forwardBar = LEYNaviView.Bar.forword(image: nil, title: "开始录制", target: self, action: #selector(forwardBarAction))
        
        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.videoGravity = .resizeAspectFill//.resizeAspect
        previewLayer.frame = contentView.bounds
        contentView.layer.addSublayer(previewLayer)
        
        let timeLabel = LEYLabel(frame: CGRect(x: 20, y: 20, width: 80, height: 30), text: "00:00:00", font: LEYFont(14), color: .red, alignment: .center)
        self.contentView.addSubview(timeLabel)
        timeLabel.backgroundColor = LEYHexColor(0x000000, 0.2)
        timeLabel.layer.cornerRadius = 6
        timeLabel.layer.masksToBounds = true
        self.timeLabel = timeLabel
    }
    
    
    func setCameraConfiguration() {
        //创建AVCaptureSession对象
        let captureSession = AVCaptureSession()
        self.captureSession = captureSession
        captureSession.sessionPreset = .high
        captureSession.beginConfiguration()
        
        //配置输入设备
        AVCaptureDevice.devices().forEach { (device) in
            if device.hasMediaType(.video) && device.position == .back {
                do {
                    let captureVideoDeviceInput = try AVCaptureDeviceInput(device: device)
                    if captureSession.canAddInput(captureVideoDeviceInput) {
                        captureSession.addInput(captureVideoDeviceInput)
                    }
                } catch {
                    
                }
            }
            
            if device.hasMediaType(.audio) {
                do {
                    let captureAudioDeviceInput = try AVCaptureDeviceInput(device: device)
                    if captureSession.canAddInput(captureAudioDeviceInput) {
                        captureSession.addInput(captureAudioDeviceInput)
                    }
                } catch {
                        
                }
            }
        }
        
        //配置输出设备
        let captureMovieFileOutput = AVCaptureMovieFileOutput()
        self.captureMovieFileOutput = captureMovieFileOutput
        if captureSession.canAddOutput(captureMovieFileOutput) {
            captureSession.addOutput(captureMovieFileOutput)
        }
        
        //设置链接管理对象
        if let captureConnection = captureMovieFileOutput.connection(with: .video) {
            captureConnection.videoScaleAndCropFactor = captureConnection.videoMaxScaleAndCropFactor  //视频旋转方向设置
            if captureConnection.isVideoStabilizationSupported {
                captureConnection.preferredVideoStabilizationMode = .auto
            }
        }
       
        captureSession.commitConfiguration()
        
        captureSession.startRunning()
    }

    @objc func forwardBarAction() {
        let title = self.naviView.forwardBar?.titleLabel?.text
        
        if title == "开始录制" {
            self.naviView.forwardBar?.updateSubviews(nil, "停止录制")
            startRecord()
        }
        
        if title == "停止录制" {
            self.naviView.forwardBar?.updateSubviews(nil, "保存")
            stopRecord()
        }
        
        if title == "保存" {
            save()
        }
    }
    
    func startRecord() {
        time_length = 0
        
        self.timer = Timer.every(Double(timer_interval).seconds) { (timer: Timer) in
            self.time_length += self.timer_interval
            
            let second = self.time_length % 60
            let minute = (self.time_length / 60) % 60
            let hour = self.time_length / 3600
            
            self.timeLabel.text = String(format: "%02ld:%02ld:%02ld", hour, minute, second)
        }
        
        //开始录制
        do {
            let documentsDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
                if let fileURL = URL(string: "leyCamera.mp4", relativeTo: documentsDir) {
                    do {
                        try FileManager.default.removeItem(at:fileURL)
                    } catch {
                    }
                    
                    self.videoUrl = fileURL
                    
                    captureMovieFileOutput.startRecording(to: fileURL, recordingDelegate: self)
                }
        } catch {
            fatalError("Couldn't initialize movie, error: \(error)")
        }
        
    }
    
    func stopRecord() {
        if let _timer = timer {
            _timer.invalidate()
        }
        
        captureMovieFileOutput.stopRecording()
        captureSession.stopRunning()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
            self.previewVideoAfterShoot()
        })
    }
    
    func save() {
        if let url = videoUrl {
            LEPHAsset.Manager.saveVideo(fileURL: url) {  (state, asset) in
                print("save video success")
                 DispatchQueue.main.async {
                    self.navigationController?.popViewController(animated: true)
                }
            }
        }
    }
    
    // 预览视频
    func previewVideoAfterShoot() {
        guard let url = videoUrl else {
            return
        }
        
        let videoPreviewContainerView = UIView(frame: self.contentView.bounds)
        self.contentView.addSubview(videoPreviewContainerView)
        videoPreviewContainerView.backgroundColor = .black
        
        let asset = AVURLAsset(url: url)
        let item = AVPlayerItem(asset: asset)
        let player = AVPlayer(playerItem: item)
        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = videoPreviewContainerView.bounds
        playerLayer.videoGravity = .resizeAspectFill //.resizeAspect
        videoPreviewContainerView.layer.addSublayer(playerLayer)
        
        player.play()
    }

}




extension AVFCameraViewController: AVCaptureFileOutputRecordingDelegate {
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        
    }
}

如果喜欢,请帮忙点赞。支持转载,转载请附原文链接。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359