iOS 视频捕获系列Swift之AVFoundation(一)
iOS 视频捕获系列之AVFoundation(一)
AVCaptureMovieFileOutput系列
在iOS开发过程中,或多或少的都涉及视频的操作。
尤其在去年直播行业的带动下,移动端对视频的处理也愈来愈发要求严格。
本文也是在 这篇 中参考而来。
Swift
版本哦!
- 本文 demo 均可在 GitHub 获得
- 本篇文章主要是让你学会以
AVCaptureMovieFileOutput
式的输出 - 欢迎关注个人 博客
blog.aiyinyu.com
本篇不涉及 :
视频输出质量
帧率设置
具体的设备格式
像素格式
光学防抖
...等等
这些都会在下一篇
中带你去认识。如果还不会用,就想了解这么多。就如同还不会走就要跑一样,是要跌大跟头滴!
正文:
在iOS当中对视频捕捉一般是 :
UIImagePickerController
- 或者
AVFoundation
本文主要内容是: AVFoundation
AVFoundation
与UIImagePickerController
的区别在于 在于对视频流的处理,显然前者会更高级一点。- 而
AVFoundation
中对视频的输出处理 又分为AVCaptureMovieFileOutput
与AVAssetWriter
。这里如果想要对视频的输出给出更多的操作那选择AVAssetWriter
是个不错的选择。 - 所以这里重点介绍
AVFoundation
更多的区别,还是在代码中体验比较好,说太多都没用。就是干
为了更好的查看其关系,下面的图比较直观一点:
t
首先我们新建一个工程
并在工程中的 plist
文件中添加访问 权限
Privacy - Camera Usage Description
Privacy - Microphone Usage Description
Privacy - Photo Library Usage Description
Privacy - Media Library Usage Description
先来研究
AVCaptureMovieFileOutput
关于 AVCaptureMovieFileOutput
看上图对号入座
首先新建一个 fileOutputViewController
控制器
控制器上放俩按钮: Close
Record
你可以用 storyboard
拖拽也可以用代码实现其点击事件
由上图我们可以看到输出方式有两种 AVCaptureMovieFileOutput
与 AVAssetWriter
,在输出之前他们对视频的操作是一样的,所以我们可以把 它俩公共的部分抽象出来一个类,对使用不同的输出方式进行继承这个类就 OK
了
相同的部分抽象成 一个继承 NSObject
的 CaptureSessionCoordinator
公共类
该公共类不对采集后的视频不做输出处理,因为输出有两种不同的处理结果。
每一种处理正是其继承 CaptureSessionCoordinator
类的 子类
完成其处理
公共类:CaptureSessionCoordinator
对 AVCaptureSession
类进行处理,相关属性如下:
AVCaptureSession
AVCaptureDevice
代理
视图
如下:
因为使用到线程,故对资源的加锁问题,在 Swift
中没法直接向 Oc
那样直接使用: synchronized
故在此利用闭包的特性达到相同的效果:
如何使用看文中代码
func synchronized(_ lock: AnyObject,dispose: ()->()) {objc_sync_enter(lock)dispose()objc_sync_exit(lock)
}
由于对视频的处理都不是在主控制器fileOutputViewController
里面执行的。故,对视频的输出都是需要代理来回调到控制器里面执行后续的相关操作。
所以这里需要一个代理:
protocol captureSessionCoordinatorDelegate: class {func coordinatorDidBeginRecording(coordinator: CaptureSessionCoordinator)func didFinishRecording(coordinator: CaptureSessionCoordinator)
}
上面的铺垫后,下面开始对 AVCaptureSession
进行相应的操作:
以我们的常识,该类中必须有这些方法:
- 开始运行
startRunning
结束运行
stopRunning
开始记录
startRecording
- 结束记录
stopRecording
- 初始化初始化
AVCaptureVideoPreviewLayer
其他的方法可以在初始中进行,也可以进行模块化拆分
该类一个完整的代码如下:
class CaptureSessionCoordinator: NSObject {var captureSession: AVCaptureSession?var cameraDevice: AVCaptureDevice?var delegateCallQueue: DispatchQueue?weak var delegate: captureSessionCoordinatorDelegate?private var sessionQueue = DispatchQueue(label: "coordinator.Session")private var previewLayer: AVCaptureVideoPreviewLayer?override init() {super.init()captureSession = setupCaptureSession()}public func setDelegate(capDelegate: captureSessionCoordinatorDelegate,queue: DispatchQueue) {synchronized(self) {delegate = capDelegateif delegateCallQueue != queue {delegateCallQueue = queue}}}//MARK: ________________Session Setup________________private func setupCaptureSession() -> AVCaptureSession {let session = AVCaptureSession()if !addDefaultCameraInputToCaptureSession(capSession: session) {printLogDebug("failed to add camera input to capture session")}if addDefaultMicInputToCaptureSession(capSession: session) {printLogDebug("failed to add mic input to capture session")}return session}private func addDefaultCameraInputToCaptureSession(capSession: AVCaptureSession) -> Bool {do {let cameraInput = try AVCaptureDeviceInput(device: AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo))let success = addInput(input: cameraInput, capSession: capSession)cameraDevice = cameraInput.devicereturn success} catch let error as NSError {printLogDebug("error configuring camera input: \(error.localizedDescription)")return false}}private func addDefaultMicInputToCaptureSession(capSession: AVCaptureSession) -> Bool {do {let micInput = try AVCaptureDeviceInput(device: AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio))let success = addInput(input: micInput, capSession: capSession)return success} catch let error as NSError {printLogDebug("error configuring mic input: \(error.localizedDescription)")return false}}//MARK: ________________Public Api________________func addInput(input: AVCaptureDeviceInput,capSession: AVCaptureSession) -> Bool {if capSession.canAddInput(input) {capSession.addInput(input)return true}printLogDebug("input error")return false}func addOutput(output: AVCaptureOutput,capSession: AVCaptureSession) -> Bool {if capSession.canAddOutput(output) {capSession.addOutput(output)return true}printLogDebug("output error")return false}func startRunning() {sessionQueue.async {self.captureSession?.startRunning()}}func stopRunning() {sessionQueue.async {self.stopRunning()self.captureSession?.stopRunning()}}func startRecording() {// 子类继承后重写}func stopRecording() {// 子类继承后重写}
}
我们先来: AVCaptureMovieFileOutput
我们创建以 AVCaptureMovieFileOutput 方式输出并继承 CaptureSessionCoordinator 的类:fileOutputCoordinator
由最上面的大图可知,AVFoundation
输出有两种:AVCaptureMovieFileOutput
与AVAssetWriter
。
而 AVCaptureMovieFileOutput
是对输出流没有做太多的处理,以AVCaptureMovieFileOutput
方式进行视频输出处理的类,不需要太多的处理。
故继承 CaptureSessionCoordinator
它的fileOutputCoordinator
子类只需如下:
重点便是对输出的处理
class fileOutputCoordinator: CaptureSessionCoordinator,AVCaptureFileOutputRecordingDelegate {var movieFileOutput: AVCaptureMovieFileOutput?override init() {super.init()movieFileOutput = AVCaptureMovieFileOutput()_ = addOutput(output: movieFileOutput!, capSession: captureSession!)}override func startRecording() {let fm = YfileManager()let tempUrl = fm.tempFileUrl()movieFileOutput?.startRecording(toOutputFileURL: tempUrl, recordingDelegate: self)}override func stopRecording() {movieFileOutput?.stopRecording()}func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {delegate?.didFinishRecording(coordinator: self, url: outputFileURL)}func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {delegate?.coordinatorDidBeginRecording(coordinator: self)}
}
上面代码中有一个对文件处理的路径操作类:YfileManager
它主要就是对文件路径的操作,与临时文件存储到系统相册中的操作:以上代码中牵扯到的只有如下:
class YfileManager: NSObject {func tempFileUrl() -> URL {var path: String = ""let fm = FileManager.defaultvar i: Int = 0while path.isEmpty || fm.fileExists(atPath: path) {path = NSTemporaryDirectory() + "output\(i.description).mov"i += 1}return URL(fileURLWithPath: path)}
/// 对临时视频文件的存储操作,本方法在iOS9以后被遗弃了func copFileToCameraRoll(fileUrl: URL) {let library = ALAssetsLibrary()if !library.videoAtPathIs(compatibleWithSavedPhotosAlbum: fileUrl) {printLogDebug("video error")}library.writeVideoAtPath(toSavedPhotosAlbum: fileUrl) { (url, error) inif (error != nil) {printLogDebug("error: \(error?.localizedDescription)")} else if url == nil {printLogDebug("url is empty")}}}
}
到目前为止涉及非控制器(fileOutputViewController)的代码全部完成,接下来我们来到控制器执行相关的操作
实现fileOutputViewController
控制器的方法
首当其冲的是相机视图与执行代理的方法:captureSessionCoordinatorDelegate
相关变量:
@IBOutlet weak var recordButton: UIBarButtonItem!var captureSessionCoordinator: fileOutputCoordinator?var recording: Bool = falsevar dismissing: Bool = false
控制器具体代码:
class fileOutputViewController: UIViewController,captureSessionCoordinatorDelegate {@IBOutlet weak var recordButton: UIBarButtonItem!var captureSessionCoordinator: fileOutputCoordinator?var recording: Bool = falsevar dismissing: Bool = falseoverride func viewDidLoad() {super.viewDidLoad()captureSessionCoordinator = fileOutputCoordinator()captureSessionCoordinator?.setDelegate(capDelegate: self, queue: DispatchQueue(label: "fileOutputCoordinator"))confiureCamper()}override func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()// Dispose of any resources that can be recreated.}/// 关闭当前视图@IBAction func closeCameral(_ sender: Any) {if recording {dismissing = true} else {stopPipelineAndDismiss()}}/// 开始记录 与停止记录@IBAction func recording(_ sender: Any) {if recording {captureSessionCoordinator?.stopRecording()} else {UIApplication.shared.isIdleTimerDisabled = true}recordButton.isEnabled = falserecordButton.title = "Stop"captureSessionCoordinator?.startRecording()recording = true}func confiureCamper() {let cameraViewlayer = captureSessionCoordinator?.previewLayerSetting()cameraViewlayer?.frame = view.boundsview.layer.insertSublayer(cameraViewlayer!, at: 0)captureSessionCoordinator?.startRunning()}func stopPipelineAndDismiss() {captureSessionCoordinator?.stopRunning()dismiss(animated: true, completion: nil)dismissing = false}func coordinatorDidBeginRecording(coordinator: CaptureSessionCoordinator) {recordButton.isEnabled = true}func didFinishRecording(coordinator: CaptureSessionCoordinator, url: URL) {UIApplication.shared.isIdleTimerDisabled = falserecordButton.title = "Record"recording = falselet fm = YfileManager()fm.copFileToCameraRoll(fileUrl: url)if dismissing {stopPipelineAndDismiss()}}}
目前为止:到目前为止一个完整的
AVCaptureMovieFileOutput
类型的输出完成
如果你在真机设备调试过程中想查看临时的 tem文件,或者沙河的文件。那就接着如下看:
Xcode上面的导航栏
->Window
->Devices
->点击你的设备
->找到右下的installed Apps
->点击你的要看的项目
->点击+ -右边图标
->Download
下载到桌面即可 然后选择显示包内容
就可以看到当前沙盒文件的状态啦!
如图:
如图
iOS 视频捕获系列Swift之AVFoundation(一)相关推荐
- iOS视频捕获入门篇
目录 1. AVFoundation简介 2. 视频捕捉 3. AVCaptureSession 4. AVCaptureDevice 5. AVCaptureDeviceInput 6. AVCap ...
- swift 快速奔跑的兔几 本节的内容是:音频与视频 第一说 AVFoundation 以及简单的iOS视频app
1.AVFoundation是一个功能强大的庞大框架,能够对音频和视频执行各种复杂操作. AVFoundation被设计用来加载和播放大量流行的视听格式. AVFoundation将可以播放的媒体称为 ...
- [共享]iOS开发系列--Swift语言
2019独角兽企业重金招聘Python工程师标准>>> iOS开发系列--Swift语言 概述 Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服 ...
- iOS仿写有妖气漫画、视频捕获框架、启动页广告页demo、多种动画效果等源码
iOS精选源码 以tableview的section为整体添加阴影效果/ta'b'le'vi'e'w顶部悬浮-. 一个可以轻松应用自定义过滤器的视频捕获框架. 基于UITableView的组件,旨在显 ...
- iOS仿写有妖气漫画、视频捕获框架、启动页广告页demo、多种动画效果等源码...
iOS精选源码 以tableview的section为整体添加阴影效果/ta'b'le'vi'e'w顶部悬浮.... 一个可以轻松应用自定义过滤器的视频捕获框架. 基于UITableView的组件,旨 ...
- C# 视频监控系列(9):服务器端——数据捕获(抓图 + 录像)
前言 录像功能是监控系统中最重要的功能之一,除了本文的功能实现外,还需要你自己考虑合适的存储策略:存储大小.时间段.存储盘符等. 注意 本系列文章限于学习交流,注重过程,由于涉及公司,所以不提供源代码 ...
- C# 视频监控系列(5):客户端——给服务器端发送字符串和录像(数据捕获)
前言 这几天加紧赶工写服务器端的程序,所有系列文章更新较慢,见谅: ) 注意 本系列文章限于学习交流,注重过程,由于涉及公司,所以不提供源代码下载,非常抱歉!!但是请大家放心,核心.实现以及其他能够贴 ...
- IOS音视频(四十三)AVFoundation 之 Audio Session
IOS音视频(四十三)AVFoundation 之 Audio Session 1.音频会话概述 2. 配置音频会话 2.1 音频会话默认行为 2.2 配置音频会话 2.3 使用多路由类别扩展选项 2 ...
- iOS开发系列--Swift语言
概述 Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服了C语言的兼容性问题.Swift发展过程中不仅保留了ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在 ...
最新文章
- 【转】《iOS7 by Tutorials》系列:iOS7的设计精髓(上)
- 性能测试应用领域分析
- 使用PHP CURL的POST数据
- InstallShield SdShowMsg未关闭导致安装程序无法停止
- 美团Android自动化之旅—生成渠道包
- linux录制远程麦克风声音,在Linux上录制麦克风到wav或mp3文件?
- common.css
- devops什么意思_devops是什么意思
- P3649 [APIO2014]回文串
- java jsp乱码怎么解决_Java/JSP中文乱码问题解决心得
- 大学四年,看过的优质书籍推荐
- 坐标系ICRS与ITRS相互转换,时间系统及转换
- ES6模板字符串中使用变量
- Node.js 包管理器 ied
- 医疗器械软件 软件生存周期过程
- Android 实践:做一款新闻 APP
- 软件设计师-设计模式
- Reporting Service:纵向合并单元格
- 推荐系统与深度学习(一)开端
- HDFS高可用集群中NameNode无法启动——解决方案
热门文章
- 《转》Python学习(14)-对文件的操作(一)
- 函数的四种调用模式.上下文调用.call.apply
- 图论:关于二分图的总结(转载)
- Memcached 缓存系统的-介绍、安装以及应用
- HttpHand和HttpModule的详细解释,包括Asp.Net对Http请求的处理流程。
- 设计模式之模板方法模式(Template Method)摘录
- 【Live555】live555源码详解(五):MediaSource、MediaSink、MediaSession、MediaSubsession
- 【C++】Google C++编码规范(二):类
- Java项目:宠物商城系统(java+Springboot+Maven+mybatis+Vue+mysql)
- linux配置文件怎么把某行后几个字符替换_vim(Linux运维)