内容简介:AVFoundation , 视频的加载与导出,大量使用异步。 简单的发消息, 肯定是不行的。阻塞当前线程, 造成卡顿。 AVFoundation 就是为了充分利用64位的硬件和多线程设计的。播放本地的视频文件, 和远程的视频与流媒体。先讲 AVKit 里面的 AVPlayerViewController. AVPlayerViewController 是 ViewController 的子类,
- 使用资源(一般就是照片库里面的视频,图片,live photo),
- 播放,
- 编辑,
- 捕捉(拍照和录视频)
- 导出资源(就处理过的资源,拍的照片,编辑的视频,导出到相册)
AVFoundation , 视频的加载与导出,大量使用异步。 简单的发消息, 肯定是不行的。阻塞当前线程, 造成卡顿。 AVFoundation 就是为了充分利用64位的硬件和多线程设计的。
首先是播放,
播放本地的视频文件, 和远程的视频与流媒体。
本地文件,单个播放
先讲 AVKit 里面的 AVPlayerViewController. AVPlayerViewController 是 ViewController 的子类,
AVPlayerViewController 在 TV OS 上,非常强大。(本文仅介绍 iOS 平台下)
苹果自带的 AVPlayerViewController 里面有很多播放的控件。 回播中,就是播放本地文件中,可以播放、暂停、快进、快退,调整视频的长宽比例( 即画面在屏幕中适中,或者铺满屏幕)。
播放视频,苹果设计的很简单,代码如下:
// 拿一个 url , 建立一个 AVPlayer 实例 let player = AVPlayer(url: "你的 url") // 再建立一个 AVPlayerViewController 实例 let playerViewController = AVPlayerViewController() playerViewController.player = queuePlayer present(playerViewController, animated: true) { playerViewController.player!.play() }// 这里有一个闭包, 界面出现了,再播放。 复制代码
本地文件,多个连续播放
连着放,使用 AVQueuePlayer,把多个视频放在一个视频队列中,依次连续播放 AVQueuePlayer 是 AVPlayer 的子类。 按顺序,播放多个资源。
AVPlayerItem 包含很多视频资源信息,除了资源定位 URI , 还有轨迹信息,视频的持续时长等。
苹果文档上说, AVPlayerItem 用于管理播放器播放的资源的计时和呈现状态。他有一个 AVAsset 播放资源的属性。
var queue = [AVPlayerItem]() let videoClip = AVPlayerItem(url: url) queue.append(videoClip) // queue 队列可以继续添加 AVPlayerItem 实例 let queuePlayer = AVQueuePlayer(items: queue) let playerViewController = AVPlayerViewController() playerViewController.player = queuePlayer present(playerViewController, animated: true) { playerViewController.player!.play() } 复制代码
iPad 中的画中画功能
iPad 中的画中画功能,通过给 AVAudioSession 支持后台音效, 在 Appdelegate
的 didFinishLaunchingWithOptions
中添加下面的这段代码,使用后台模式, 首先在Xcode 的 target 的 Capability 中勾选相关的后台功能。
let session = AVAudioSession.sharedInstance() do { try session.setCategory(AVAudioSessionCategoryPlayback) try session.setActive(true) } catch let error { print("AVFoundation configuration error: \(error.localizedDescription) \n\n AV 配置 有问题") } // 很有必要这样,因为画中画的视频功能,apple 是当后台任务处理的。 复制代码
流媒体播放和网络视频播放
本地的资源路径 URL ,替换为网络的 URL, 就可以了。
优化,播放完成后,退出播放界面
override func viewDidLoad() { super.viewDidLoad() // 添加播放完成的监听 NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil) } // 执行退出的界面控制 @objc func playerItemDidReachEnd(){ self.presentedViewController?.dismiss(animated: true, completion: {}) } 复制代码
接着来, 拍照, 设置捕捉的 session ,并实时预览。
设置前后摄像头,聚焦与曝光,拍照(静态图片)
摄像用到的核心类是 AVCaptureSession ,应用和 iOS 建立一个视频流的会话。 AVCaptureSession 作为调度中心, 控制设备的输入/输出流, 具体就是相机和麦克风。
AVCaptureDeviceInput 类是视频流的输入源,预览界面呈现的就是他的数据,导出的视频文件也是他负责的。 视频流 session 对象生成后,可以重新配置。这就可以动态修改视频流 session 的配置信息。视频流 session 的输入输出的路由,也可以动态改。例如,只需要一个 session. 可以通过 AVCapturePhotoOutput 导出照片,可以导出视频文件 AVCaptureMovieFileOutput.
开启视频会话
captureSession.startRunning() 之前,先要添加输入 AVCaptureDeviceInput 和输出 AVCapturePhotoOutput/AVCaptureMovieFileOutput,准备预览界面 AVCaptureVideoPreviewLayer
// 有一个 captureSession 对象 let captureSession = AVCaptureSession() // 两个输出,输出照片, 和输出视频 let imageOutput = AVCapturePhotoOutput() let movieOutput = AVCaptureMovieFileOutput() func setupSession() -> Bool{ captureSession.sessionPreset = AVCaptureSession.Preset.high // 首先设置 session 的分辨率 。sessionPreset 属性,设置了输出的视频的质量 let camera = AVCaptureDevice.default(for: .video) // 默认的相机是 back-facing camera 朝前方拍摄, 不是自拍的。 do { let input = try AVCaptureDeviceInput(device: camera!) if captureSession.canAddInput(input){ captureSession.addInput(input) activeInput = input // 添加拍照, 录像的输入 } } catch { print("Error settings device input: \(error)") return false } // 设置麦克风 let microphone = AVCaptureDevice.default(for: .audio) do{ let micInput = try AVCaptureDeviceInput(device: microphone!) if captureSession.canAddInput(micInput){ captureSession.addInput(micInput) // 添加麦克风的输入 } }catch{ print("Error setting device audio input: \(String(describing: error.localizedDescription))") fatalError("Mic") } // 添加两个输出,输出照片, 和输出视频 if captureSession.canAddOutput(imageOutput){ captureSession.addOutput(imageOutput) } if captureSession.canAddOutput(movieOutput){ captureSession.addOutput(movieOutput) } return true } 复制代码
设置视频会话的预览界面
AVCaptureVideoPreviewLayer 是 CALayer 的子类,用于展示相机拍的界面。
func setupPreview() { // 配置预览界面 previewLayer previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) // previewLayeris 通过 captureSession 初始化 // 再设置相关属性, 尺寸和视频播放时的拉伸方式 videoGravity previewLayer.frame = camPreview.bounds previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill camPreview.layer.addSublayer(previewLayer) // camPreview 是一个 UIView ,铺在 self.view 上面 } 复制代码
拍, startSession
启动视频流的方法,启动了,就不用管。没启动,就处理。 启动视频流是耗时操作,为不阻塞主线程,一般用系统默认线程队列作异步。
let videoQueue = DispatchQueue.global(qos: .default) func startSession(){ if !captureSession.isRunning{ videoQueue.async { self.captureSession.startRunning() } } } 复制代码
拍照片,下面的代码是拍摄静态照片 JPEG,不是 Live Photo.
var outputSetting = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg]) // 静态图的配置 func capturePhoto() { guard PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized else{ PHPhotoLibrary.requestAuthorization(requestAuthorizationHander) return } let settings = AVCapturePhotoSettings(from: outputSetting) imageOutput.capturePhoto(with: settings, delegate: self) // imageOutput 输出流里面的采样缓冲中,捕获出静态图 } extension ViewController: AVCapturePhotoCaptureDelegate{ func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { // 如果视频流的采样缓冲里面有数据,就拆包 if let imageData = photo.fileDataRepresentation(){ let image = UIImage(data: imageData) let photoBomb = image?.penguinPhotoBomb(image: image!) self.savePhotoToLibrary(image: photoBomb!) // 最后,合成照片保存到系统相册 // 这里有一个照片合成,具体见下面的 Github Repo. } else{ print("Error capturing photo: \(String(describing: error?.localizedDescription))") } } } 复制代码
到自拍了,就是支持前置摄像头,front-facing camera.
首先,要确认手机要有多个摄像头。有多个,才可以切换摄像头输入。 具体套路就是开始配置,修改,与提交修改。
captureSession.beginConfiguration() ,接着写修改,直到 captureSession.commitConfiguration() 提交了,才生效。
类似的还有 UIView 渲染机制。 CATransaction, 开始,设置,提交,就可以在屏幕上看到刷新的界面了。
// 配置拍摄前面(自拍),拍后面 @IBAction func switchCameras(_ sender: UIButton) { guard movieOutput.isRecording == false else{ return } // 确认手机要有多个摄像头 guard let frontCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front), let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else{ return; } // 创建新的 AVCaptureDeviceInput ,来切换。更新 captureSession 的配置。 do{ var input: AVCaptureDeviceInput? // 通过识别当前的摄像头,找出另一个(我们需要的) if activeInput.device == frontCamera{ input = try AVCaptureDeviceInput(device: backCamera) } else{ input = try AVCaptureDeviceInput(device: frontCamera) } // 得到了新的输入源,就可以开始配置了 captureSession.beginConfiguration() // 去掉旧的输入源,即不让当前的摄像头输入 captureSession.removeInput(activeInput) // 增加新的输入源,即让其他的摄像头输入 if captureSession.canAddInput(input!){ captureSession.addInput(input!) activeInput = input } // captureSession.beginConfiguration() 之后,就开始修改,直到下一句提交了,才生效。 captureSession.commitConfiguration() }catch{ print("Error , switching cameras: \(String(describing: error))") } } 复制代码
聚焦功能 POI : 点击屏幕,拍照聚焦到兴趣点
具体实现是把屏幕 UI 坐标,也就是预览图层的坐标,转换到相机的坐标系中, 再用预览图层的坐标点,设置聚焦的 point 和 mode 。
配置聚焦,属于用户输入,并要用到手机的摄像头硬件。配置 POI 的时候,可能有干扰 ( 比如后台进程的影响 ),这样就要用设备锁定了。 device.lockForConfiguration()
注意: 自拍是不可以聚焦的。前置摄像头,没有 POI 功能。
@objc func tapToFocus(recognizer: UIGestureRecognizer){ if activeInput.device.isFocusPointOfInterestSupported{ // 得到屏幕中点击的坐标,转化为预览图层里的坐标点 let point = recognizer.location(in: camPreview) // 将预览图层中的坐标点,转换到相机的坐标系中 let pointOfInterest = previewLayer.captureDevicePointConverted(fromLayerPoint: point) // 自由设置相关 UI showMarkerAtPoint(point: point, marker: focusMarker) focusAtPoint(pointOfInterest) } } // 用预览图层的坐标点,配置聚焦。 func focusAtPoint(_ point: CGPoint){ let device = activeInput.device // 首先判断手机能不能聚焦 if device.isFocusPointOfInterestSupported , device.isFocusModeSupported(.autoFocus){ do{ // 锁定设备来配置 try device.lockForConfiguration() device.focusPointOfInterest = point device.focusMode = .autoFocus device.unlockForConfiguration() // 配置完成,解除锁定 } catch{ print("Error focusing on POI: \(String(describing: error.localizedDescription))") } } } 复制代码
拍照曝光功能,双击设置曝光坐标
类似聚焦,具体实现是把屏幕 UI 坐标,也就是预览图层的坐标,转换到相机的坐标系中, 再用预览图层的坐标点,设置曝光的 point 和 mode 。 同聚焦不一样,曝光要改两次 mode.
mode 从默认锁定的 .locked 到选定坐标点的连续自动曝光 .continuousAutoExposure, 最后系统调好了,再切换回默认的锁定 .locked 。 因为不知道系统什么时候连续自动曝光处理好,所以要用到 KVO. 监听 activeInput.device 的 adjustingExposure 属性。 当曝光调节结束了,就锁定曝光模式。
( 调用时机挺好的, 双击屏幕,手机摄像头自动曝光的时候,就防止干扰。曝光完成后,马上改曝光模式为锁定 。这样就不会老是处在曝光中。)
(这个有点像监听键盘,那里一般用系统通知。)
配置曝光,属于用户输入,并要用到手机的摄像头硬件。配置曝光的时候,可能有干扰 ( 比如后台进程的影响 ),这样就要用锁了。 device.lockForConfiguration()
其他: 自拍是有曝光效果的
// 单指双击,设置曝光, 更多见下面的 github repo @objc func tapToExpose(recognizer: UIGestureRecognizer){ if activeInput.device.isExposurePointOfInterestSupported{ // 与聚焦一样,得到屏幕中点击的坐标,转化为预览图层里的坐标点 let point = recognizer.location(in: camPreview) // 将预览图层中的坐标点,转换到相机的坐标系中 let pointOfInterest = previewLayer.captureDevicePointConverted(fromLayerPoint: point) showMarkerAtPoint(point: point, marker: exposureMarker) exposeAtPoint(pointOfInterest) } } private var adjustingExposureContext: String = "Exposure" private let kExposure = "adjustingExposure" func exposeAtPoint(_ point: CGPoint){ let device = activeInput.device if device.isExposurePointOfInterestSupported, device.isFocusModeSupported(.continuousAutoFocus){ do{ try device.lockForConfiguration() device.exposurePointOfInterest = point device.exposureMode = .continuousAutoExposure // 先判断手机,能不能锁定曝光。可以就监听手机摄像头的调整曝光属性 if device.isFocusModeSupported(.locked){ // 同聚焦不一样,曝光要改两次 mode. // 这里有一个不受控制的耗时操作( 不清楚什么时候系统处理好),需要用到 KVO device.addObserver(self, forKeyPath: kExposure, options: .new, context: &adjustingExposureContext) // 变化好了, 操作结束 device.unlockForConfiguration() } } catch{ print("Error Exposing on POI: \(String(describing: error.localizedDescription))") } } } // 使用 KVO override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { // 先确认,监听到的是指定的上下文 if context == &adjustingExposureContext { let device = object as! AVCaptureDevice // 如果手机摄像头不处于曝光调整中,也就是完成曝光了,就可以处理了 if !device.isAdjustingExposure , device.isExposureModeSupported(.locked){ // 观察属性,变化了, 一次性注入调用, 就销毁 KVO // 然后到主队列中异步配置 device.removeObserver(self, forKeyPath: kExposure, context: &adjustingExposureContext) DispatchQueue.main.async { do{ // 完成后,将曝光状态复原 try device.lockForConfiguration() device.exposureMode = .locked device.unlockForConfiguration() } catch{ print("Error exposing on POI: \(String(describing: error.localizedDescription))") } } } } else{ super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First HTML与CSS(第2版)
Elisabeth Robson、Eric Freeman / 徐阳、丁小峰 / 中国电力出版社 / 2013-9 / 98.00元
是不是已经厌倦了那些深奥的HTML书?你可能在抱怨,只有成为专家之后才能读懂那些书。那么,找一本新修订的《Head First HTML与CSS(第2版)》吧,来真正学习HTML。你可能希望学会HTML和CSS来创建你想要的Web页面,从而能与朋友、家人、粉丝和狂热的顾客更有效地交流。你还希望使用最新的HTML5标准,能够保证随时间维护和扩展你的Web页面,使它们在所有浏览器和移动设备中都能正常工......一起来看看 《Head First HTML与CSS(第2版)》 这本书的介绍吧!