内容简介:拍照是手机的重要用途,有必要了解下拍照、视频处理相关。处理 AVFoundation,套路就是配置 session, 添加输入输出, 把视频流的管道打通。 用 device 作为输入,获取信息,用 session 作为输入输出的桥梁,控制与调度,最后指定我们想要的输出类型。 拍视频与拍照不同,会有声音,输入源就要加上麦克风了拍视频的代码如下:
拍照是手机的重要用途,有必要了解下拍照、视频处理相关。
拍视频,把视频文件导出到相册
处理 AVFoundation,套路就是配置 session, 添加输入输出, 把视频流的管道打通。 用 device 作为输入,获取信息,用 session 作为输入输出的桥梁,控制与调度,最后指定我们想要的输出类型。 拍视频与拍照不同,会有声音,输入源就要加上麦克风了 AVCaptureDevice.default(for: .audio)
,视频流的输出就要用到 AVCaptureMovieFileOutput 类了。
拍视频的代码如下:
func captureMovie() { // 首先,做一个确认与切换。当前摄像头不在拍摄中,就拍摄 guard movieOutput.isRecording == false else { print("movieOutput.isRecording\n") stopRecording() return; } // 获取视频输出的连接 let connection = movieOutput.connection(with: .video) // 控制连接的方位,视频的横竖屏比例与手机的一致 // 点击拍摄按钮拍摄的这一刻,根据当前设备的方向来设置录像的方向 if (connection?.isVideoOrientationSupported)!{ connection?.videoOrientation = currentVideoOrientation() } // 设置连接的视频自动稳定,手机会选择合适的拍摄格式和帧率 if (connection?.isVideoStabilizationSupported)!{ connection?.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto } let device = activeInput.device // 因为需要摄像头能够灵敏地聚焦 if device.isSmoothAutoFocusSupported{ do{ try device.lockForConfiguration() device.isSmoothAutoFocusEnabled = false // 如果设置为 true, lens movements 镜头移动会慢一些 device.unlockForConfiguration() }catch{ print("Error setting configuration: \(String(describing: error.localizedDescription))") } } let output = URL.tempURL movieOutput.startRecording(to: output!, recordingDelegate: self) } 复制代码
与拍照不同,录像使用的是连接, movieOutput.connection(with: .video)
.
拍视频,自然会有完成的时候,
在 AVCaptureFileOutputRecordingDelegate
类的代理方法里面,保存视频文件,更新 UI
outputFileURL 参数, 是系统代理完成回调给开发者的,系统把视频文件写入 app 沙盒的资源定位符。要做的是把沙盒里面的视频文件,拷贝到系统相册。
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { if let error = error{ print("Error, recording movie: \(String(describing: error.localizedDescription))") } else{ // 保存到相册, 具体代码见 github repo saveMovieToLibrary(movieURL: outputFileURL) // 更改 UI captureButton.setImage(UIImage(named: "Capture_Butt"), for: .normal) // 停止计时器 stopTimer() } } 复制代码
拍视频的时候,能够知道录的怎么样了,比较好。
用计时器记录,有一个 Label 展示
func startTimer(){ // 销毁旧的 if updateTimer != nil { updateTimer.invalidate() } // 开启新的 updateTimer = Timer(timeInterval: 0.5, target: self, selector: #selector(self.updateTimeDisplay), userInfo: nil, repeats: true) RunLoop.main.add(updateTimer, forMode: .commonModes) } 复制代码
拍照环境较暗,就要亮灯了,都是调整 AVCaptureDevice 类里的属性。
拍照用闪光灯, 用 flashMode, 配置 AVCapturePhotoSettings。 每次拍照,都要新建 AVCapturePhotoSettings.AVCapturePhotoSettings 具有原子性 atomic.
拍视频用手电筒, 用 TorchMode, 配置的是 device.torchMode 直接修改 AVCaptureDevice 的属性
苹果设计的很好。输出类型决定亮灯模式。 拍照用闪光灯,是按瞬间动作配置。 拍视频,就是长亮了。
// MARK: Flash Modes (Still Photo), 闪光灯 func setFlashMode(isCancelled: Bool = false) { let device = activeInput.device // 闪光灯, 只有后置摄像头有。 前置摄像头是,增加屏幕亮度 if device.isFlashAvailable{ // 这段代码, 就是控制闪光灯的 off, auto , on 三种状态, 来回切换 var currentMode = currentFlashOrTorchMode().mode currentMode += 1 if currentMode > 2 || isCancelled == true{ currentMode = 0 } let new_mode = AVCaptureDevice.FlashMode(rawValue: currentMode) self.outputSetting.flashMode = new_mode!; flashLabel.text = currentFlashOrTorchMode().name } } // MARK: Torch Modes (Video), 手电筒 func setTorchMode(isCancelled: Bool = false) { let device = activeInput.device if device.hasTorch{ // 这段代码, 就是控制手电筒的 off, auto , on 三种状态, 来回切换 var currentMode = currentFlashOrTorchMode().mode currentMode += 1 if currentMode > 2 || isCancelled == true{ currentMode = 0 } let new_mode = AVCaptureDevice.TorchMode(rawValue: currentMode) if device.isTorchModeSupported(new_mode!){ do{ // 与前面操作类似,需要 lock 一下 try device.lockForConfiguration() device.torchMode = new_mode! device.unlockForConfiguration() flashLabel.text = currentFlashOrTorchMode().name }catch{ print("Error setting flash mode: \(String(describing: error.localizedDescription))") } } } } 复制代码
视频合成,将多个音频、视频片段合成为一个视频文件。给视频增加背景音乐
合成视频, 操作的就是视频资源, AVAsset .
AVAsset 的有一个子类 AVComposition . 一般通过 AVComposition 的子类 AVMutableComposition 合成视频。
AVComposition 可以把多个资源媒体文件,在时间上自由安排,合成想要的视频。 具体的就是借助一组音视频轨迹 AVMutableCompositionTrack。
AVCompositionTrack 包含一组轨迹的片段。AVCompositionTrack 的子类 AVMutableCompositionTrack,可以增删他的轨迹片段,也可以调整轨迹的时间比例。
拿 AVMutableCompositionTrack 添加视频资源 AVAsset, 作为轨迹的片段。
用 AVPlayer 的实例预览合成的视频资源 AVCompositions, 用 AVAssetExportSession 导出合成的文件。
预览合成的视频
套路就是拿资源的 URL 创建 AVAsset。 拍的视频 AVAsset 包含音频信息(背景音,说话的声音, 单纯的噪音)和视频信息。
用 AVComposition 的子类 AVMutableComposition,添加音轨 composition.addMutableTrack(withMediaType: .audio
和视频轨迹 composition.addMutableTrack(withMediaType: .video
var previewURL: URL? // 记录直接合成的文件地址 @IBAction func previewComposition(_ sender: UIButton) { // 首先要合成, // 要合成,就得有资源, 并确保当前没有进行合成的任务 guard videoURLs.count > 0 , activityIndicator.isAnimating == false else{ return } // 最后就很简单了, 拿资源播放 var player: AVPlayer! defer { let playerViewController = AVPlayerViewController() playerViewController.allowsPictureInPicturePlayback = true playerViewController.player = player present(playerViewController, animated: true) { playerViewController.player!.play() } } guard previewURL == nil else { player = AVPlayer(url: previewURL!) return } // 之前, 没合成写入文件, 就合成预览 var videoAssets = [AVAsset]() // 有了 视频资源的 URL, AVMutableComposition 使用的是 AVAsset // 拿视频资源的 URL , 逐个创建 AVAsset for urlOne in videoURLs{ let av_asset = AVAsset(url: urlOne) videoAssets.append(av_asset) } // 用 AVComposition 的子类 AVMutableComposition, 来修改合成的轨迹 let composition = AVMutableComposition() // 创建两条轨迹, 音轨轨迹和视频轨迹 let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) let audioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) var startTime = kCMTimeZero // 遍历刚才创建的 AVAsset, 放入 AVComposition 添加的音轨和视频轨迹中 for asset in videoAssets{ do{ // 插入视频轨迹 try videoTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.duration), of: asset.tracks(withMediaType: .video)[0], at: startTime) }catch{ print("插入合成视频轨迹, 视频有错误") } do{ // 插入音轨, try audioTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.duration), of: asset.tracks(withMediaType: .audio)[0], at: startTime) }catch{ print("插入合成视频轨迹, 音频有错误") } // 让媒体文件一个接一个播放,更新音轨和视频轨迹中的开始时间 startTime = CMTimeAdd(startTime, asset.duration) } let playItem = AVPlayerItem(asset: composition) player = AVPlayer(playerItem: playItem) } 复制代码
合成视频中,更加精细的控制, 通过 AVMutableVideoCompositionLayerInstruction
AVMutableVideoCompositionLayerInstruction 这个类, 可以调整合成轨迹的变形(平移和缩放)、裁剪和透明度等属性。
设置 AVMutableVideoCompositionLayerInstruction 一般需要两个参数,
AVMutableVideoCompositionLayerInstruction 通过轨迹来创建 let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
.
通过资源文件 AVAsset 的信息配置。
一般拍照的屏幕是 375X667 , 相对视频的文件的长度比较小。视频的文件宽度高度 1280.0 X 720.0, 远超手机屏幕 。需要做一个缩小
func videoCompositionInstructionForTrack(track: AVCompositionTrack, asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction{ let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track) let assetTrack = asset.tracks(withMediaType: .video)[0] // 通过视频文件 asset 的 preferredTransform 属性,了解视频是竖着的,还是横着的,区分处理 let transfrom = assetTrack.preferredTransform // orientationFromTransform() 方法,见 github repo let assetInfo = transfrom.orientationFromTransform() // 为了屏幕能够呈现高清的横向视频 var scaleToFitRatio = HDVideoSize.width / assetTrack.naturalSize.width if assetInfo.isPortrait { // 竖向 scaleToFitRatio = HDVideoSize.height / assetTrack.naturalSize.width let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio) let concatTranform = assetTrack.preferredTransform.concatenating(scaleFactor) instruction.setTransform(concatTranform, at: kCMTimeZero) } else{ // 横向 let scale_factor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio) let scale_factor_two = CGAffineTransform(rotationAngle: .pi/2.0) let concat_transform = assetTrack.preferredTransform.concatenating(scale_factor).concatenating(scale_factor_two) instruction.setTransform(concat_transform, at: kCMTimeZero) } // 将处理好的 AVMutableVideoCompositionLayerInstruction 返回 return instruction } 复制代码
视频合成,并导出到相册。 这是一个耗时操作
导出的套路是拿 AVMutableComposition, 创建 AVAssetExportSession, 用 AVAssetExportSession 对象的 exportAsynchronously
方法导出。 直接写入到相册,对应的 URL 是 session.outputURL
// 视频合成,并导出到相册。 这是一个耗时操作 private func mergeAndExportVideo(){ activityIndicator.isHidden = false // 亮一朵菊花, 给用户反馈 activityIndicator.startAnimating() // 把记录的 previewURL 置为 nil // 视频合成, 导出成功, 就赋新值 previewURL = nil // 先创建资源 AVAsset var videoAssets = [AVAsset]() for url_piece in videoURLs{ let av_asset = AVAsset(url: url_piece) videoAssets.append(av_asset) } // 创建合成的 AVMutableComposition 对象 let composition = AVMutableComposition() // 创建 AVMutableComposition 对象的音轨 let audioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) // 通过 AVMutableVideoCompositionInstruction ,调整合成轨迹的比例、位置、裁剪和透明度等属性。 // AVMutableVideoCompositionInstruction 对象, 控制一组 layer 对象 AVMutableVideoCompositionLayerInstruction let mainInstruction = AVMutableVideoCompositionInstruction() var startTime = kCMTimeZero // 遍历每一个视频资源,添加到 AVMutableComposition 的音轨和视频轨迹 for asset in videoAssets{ // 因为 AVMutableVideoCompositionLayerInstruction 对象适用于整个视频轨迹, // 所以这里一个资源,对应一个轨迹 let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) do{ try videoTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.duration), of: asset.tracks(withMediaType: .video)[0], at: startTime) }catch{ print("Error creating Video track.") } // 有背景音乐,就不添加视频自带的声音了 if musicAsset == nil { // 插入音频 do{ try audioTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.duration), of: asset.tracks(withMediaType: .audio)[0], at: startTime) } catch{ print("Error creating Audio track.") } } // 添加了资源,就创建配置文件 AVMutableVideoCompositionLayerInstruction let instruction = videoCompositionInstructionForTrack(track: videoTrack!, asset: asset) instruction.setOpacity(1.0, at: startTime) if asset != videoAssets.last{ instruction.setOpacity(0.0, at: CMTimeAdd(startTime, asset.duration)) // 视频片段之间, 都添加了过渡, 避免片段之间的干涉 } mainInstruction.layerInstructions.append(instruction) // 这样, mainInstruction 就添加好了 startTime = CMTimeAdd(startTime, asset.duration) } let totalDuration = startTime // 有背景音乐,给合成资源插入音轨 if musicAsset != nil { do{ try audioTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, totalDuration), of: musicAsset!.tracks(withMediaType: .audio)[0], at: kCMTimeZero) } catch{ print("Error creating soundtrack total.") } } // 设置 mainInstruction 的时间范围 mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, totalDuration) // AVMutableVideoComposition 沿着时间线,设置视频轨迹如何合成 // AVMutableVideoComposition 配置了大小、持续时间,合成视频帧的渲染间隔, 渲染尺寸 let videoComposition = AVMutableVideoComposition() videoComposition.instructions = [mainInstruction] videoComposition.frameDuration = CMTimeMake(1, 30) videoComposition.renderSize = HDVideoSize videoComposition.renderScale = 1.0 // 拿 composition ,创建 AVAssetExportSession let exporter: AVAssetExportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)! // 配置输出的 url exporter.outputURL = uniqueURL // 设定输出格式, quick time movie file exporter.outputFileType = .mov // 优化网络播放 exporter.shouldOptimizeForNetworkUse = true exporter.videoComposition = videoComposition // 开启输出会话 exporter.exportAsynchronously { DispatchQueue.main.async { self.exportDidFinish_deng(session: exporter) } } } 复制代码
以上所述就是小编给大家介绍的《AVFoundation 视频常用套路: 视频合成与导出,拍视频手电筒,拍照闪光灯》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- AVFoundation 视频常用套路: 视频合成与导出,拍视频手电筒,拍照闪光灯
- GuiLite 3.5 发布:视频,视频,视频
- .NET 处理视频-MediaInfo 获取视频信息
- Android 音视频开发打怪升级之音视频硬解码篇(一):音视频基础知识
- .NET 处理视频-ffmpeg.exe 获取视频信息
- HTML5 视频播放(video),JavaScript控制视频
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。