美文网首页相册
Live Photo实况照片的预录制设计开发思路

Live Photo实况照片的预录制设计开发思路

作者: 咸鱼有只喵 | 来源:发表于2018-09-04 15:36 被阅读71次

    简介

    首先我们需要了解在使用LivePhoto是会有这样的效果,我们点击拍照键,系统会为我们展示一段视频和一张封面图。
    如果只是视频和图片我们可简单地将其合称为一段LivePhoto,但是最难实现的效果就是,系统为我们展示的3秒视频,前面1.5秒是在你按拍照键之前的。基于这一点我们要实现预录制的功能。

    实现方案

    要实现这一效果,我们通常会想到,在我点击实况按钮或者进入我的相机App的时候就开始录制视频,点击拍照键后1.5秒就结束录制,最终裁剪最后的三秒。这么做也未尝不可,但是带来是后果是如果在live页面停留太久,低内存的机器的缓冲区会满,造成崩溃。
    我在stackoverflow上提问后,有人告诉我,可以维护一个缓冲区,缓冲区内只保存最近的1.5s视频,拍摄完成再进行拼接。

    那么问题来了,缓冲区该如何设计呢?怎么才能实现只保存最近1.5秒这一效果呢?

    1.设置缓存队列

    我经过思考,认为,维护一个视频队列是最合适的,也是较容易去实现的。
    我在用户点击实况按钮时,开始录制,但是所不同的是,我会设置一个计时器,每过1.5秒就停止录制,存到缓存区队列,然后继续录制。如果队列的长度超过2,那么就把队首的一段视频删除。
    这样就保证了缓冲区一定存在我拍照前1.5秒的视频,也不会造成缓存区满的状况。

    2.点击拍照键的操作

    用户点击拍照键时,立即停止录制,保存,然后开始录制1.5秒的视频。这样就有了后面两段视频。
    我简单的画了个示意图:
    字丑,凑合着看:


    IMG_20180904_144636.jpg

    也就是说,我点击拍照键的一瞬间,肯定不是恰好的1.5秒视频录制结束的时候,比如是0.6秒,那么我把这三段视频保存,三段视频即可合成一段LivePhoto。

    所以,核心思想就是用队列保存三段视频,最后只对这三段视频进行裁切合并。

    3.具体实现代码

    这里给出部分代码。项目来自于AppStore上线项目#折纸相机#,希望大家多多支持。
    最新版本尚未更新预录制livephoto功能。

    变量定义

        //判断是否正在拍摄livePhoto
        var isLivePhoto = false
        //第一段计时器
        var liveTimer:Timer?
        //第二段计时器
        var liveTimer2:Timer?
        var liveCounter:Double = 0.5;
        var liveCounter2:Double = 0.5;
        var liveUrl:URL!
        var videoUrls = [URL]()
        var saveManager:SaveVieoManager?
    

    具体核心代码

        func setLiveMode(){
            if !isLivePhoto{
                isLivePhoto = true
                setLiveStart()
            }else{
                isLivePhoto = false
                movieWriter?.finishRecording()
                liveTimer?.invalidate()
                liveTimer = nil
            }
        }
        
        /// 开始录制LivePhoto
        func setLiveStart(){
            //shotButton.isUserInteractionEnabled = false
            startRecord()
            videoUrls.append(videoUrl!)
            self.topView.liveCounter.isHidden = false
            topView.setCounter(text: "0")
            liveTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(updateLiveCounter), userInfo: nil, repeats: true)
           // liveTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateLiveCounter), userInfo: nil, repeats: true)
        }
        //倒计时控制
        @objc func updateLiveCounter(){
            liveCounter = liveCounter + 0.5
            print("正在拍摄LivePhoto: ",liveCounter)
            topView.setCounter(text: "\(liveCounter)")
    //        if liveCounter == 3{
    //            finishLiveRecord()
    //        }
            
            if liveCounter == 1.5{
                movieWriter?.finishRecording()
                deleteLiveBuffer()
                startRecord()
                videoUrls.append(videoUrl!)
                liveCounter = 0
            }
        }
        
        /// 倒计时结束,结束录制
        func finishLiveRecord(){
            movieWriter?.finishRecording()
            shotButton.isUserInteractionEnabled = false
            liveTimer?.invalidate()
            startRecord()
            liveCounter2 = 0
            liveTimer2 = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(setIntervalFinish), userInfo: nil, repeats: true)
            
        }
        
        
        
        //录制完成,进行合成和跳转
        @objc func setIntervalFinish(){
            liveCounter2 = liveCounter2 + 0.5
            if liveCounter2 == 1.5{
                deleteAdditionalBuffer()
                shotButton.isUserInteractionEnabled = false
                movieWriter?.failureBlock = {
                    Error in
                    print(Error as Any)
                }
               // movieWriter?.completionBlock = {
                    self.videoUrls.append(self.videoUrl!)
                    self.movieWriter?.finishRecording()
                    print("-------------:",self.videoUrls)
                    self.topView.liveCounter.isHidden = true
                    self.shotButton.isUserInteractionEnabled = true
                    self.liveTimer = nil
                    self.liveTimer2?.invalidate()
                    self.liveTimer2 = nil
                    self.liveCounter2 = 0
                setLiveStart()
                //视频合成
               // ProgressHUD.show("合成中")
                saveManager = SaveVieoManager(urls: videoUrls)
                let newUrl = URL(fileURLWithPath: "\(NSTemporaryDirectory())folder_all.mp4")
                unlink(newUrl.path)
                videoUrl = newUrl
                
                //视频裁剪以及合成
                saveManager?.combineLiveVideos(success: {
                    com in
                    self.saveManager?.store(com, storeUrl: newUrl, success:{
                        DispatchQueue.main.async {
                            let vc =  CheckViewController.init(image: nil, type: 2)
                            vc.videoUrl = newUrl
                            weak var weakSelf = self
                            vc.videoScale = weakSelf?.scaleRate
                            vc.willDismiss = {
                                //将美颜状态重置
                                if (weakSelf?.isBeauty)!{
                                    weakSelf?.isBeauty = false
                                    weakSelf?.defaultBottomView.beautyButton.isSelected = false
                                }
                                //使用闭包,在vc返回时将底部隐藏,点击切换时在取消隐藏
                                if weakSelf?.scaleRate != 0{
                                    // weakSelf?.scaleRate = 0
                                    weakSelf?.defaultBottomView.backgroundColor = UIColor.clear
                                }
                                //LivePhoto录像状态重置
                                
                            }
                            // ProgressHUD.showSuccess("合成成功")
                            //  weakSelf?.videoUrls.removeAll()
                            weakSelf?.present(vc, animated: true, completion: nil)
                            //self.setLiveStart()
                        }
                        
                    })
                })
                
                
             
            }
    
        }
        
        func deleteLiveBuffer(){
            
            if videoUrls.count>=2{
                do {
                    try FileManager.default.removeItem(atPath: (videoUrls.first!.path))
                    videoUrls.removeFirst()
                } catch {
                }
            }
            
        }
        
        
        func deleteAdditionalBuffer(){
            while videoUrls.count>=3{
                do {
                    try FileManager.default.removeItem(atPath: (videoUrls.first!.path))
                    videoUrls.removeFirst()
                } catch {
                }
            }
        }
    
    

    视频裁剪和合成部分代码:

    裁剪视频

     /// 剪辑视频
        ///
        /// - Parameters:
        ///   - frontOffset: 前面剪几秒
        ///   - endOffset: 后面剪几秒
        ///   - index: url的下标
        /// - Returns: 合成
        func cutLiveVideo(frontOffset:Float64,endOffset:Float64,index:Int)->AVMutableComposition{
            let composition = AVMutableComposition()
            // Create the video composition track.
            let compositionVideoTrack: AVMutableCompositionTrack? = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
            // Create the audio composition track.
            let compositionAudioTrack: AVMutableCompositionTrack? = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
            let pathUrl = videoUrls[index]
            let asset = AVURLAsset(url: pathUrl, options: nil)
            
            let videoTrack: AVAssetTrack = asset.tracks(withMediaType: .video)[0]
            let audioTrack: AVAssetTrack = asset.tracks(withMediaType: .audio)[0]
            compositionVideoTrack?.preferredTransform = videoTrack.preferredTransform
            
            // CMTime
            let trackDuration: CMTime = videoTrack.timeRange.duration
            let trackTimescale: CMTimeScale = trackDuration.timescale
            // 用timescale构造前后截取位置的CMTime
            let startTime: CMTime = CMTimeMakeWithSeconds(frontOffset, trackTimescale)
            let endTime: CMTime = CMTimeMakeWithSeconds(endOffset, trackTimescale)
            let intendedDuration: CMTime = CMTimeSubtract(asset.duration, CMTimeAdd(startTime, endTime))
    
            try? compositionVideoTrack?.insertTimeRange(CMTimeRangeMake(startTime, intendedDuration), of: videoTrack, at: kCMTimeZero)
            try? compositionAudioTrack?.insertTimeRange(CMTimeRangeMake(startTime, intendedDuration), of: audioTrack, at: kCMTimeZero)
    
            return composition
        
        }
        
    
    

    合成视频

    func combineLiveVideos(success:@escaping(_ mixComposition:AVMutableComposition)->()){
    
            for i in 0...videoUrls.count-1{
                //剪第一段
                if i == 0{
                    //求liveVideo第二段长度n,需要用1.5 - 此长度作为第一段需要减去的长度
                    if videoUrls.count >= 1{
                        var videoAsset2:AVURLAsset?
                        videoAsset2 = AVURLAsset.init(url: videoUrls[1])
                        let tt = videoAsset2!.duration
                        let getLengthOfVideo2 = Double(tt.value)/Double(tt.timescale)
                        //减掉开始 n 秒
                        let video1Composition = cutLiveVideo(frontOffset: getLengthOfVideo2, endOffset: 0.0, index: 0)
                        let newUrl = URL(fileURLWithPath: "\(NSTemporaryDirectory())foldercut_1.mp4")
                        unlink(newUrl.path)
                        videoUrls[0] = newUrl
                        //裁剪完第一段视频后,开始进行三段视频的合成
                        store(video1Composition, storeUrl: newUrl, success: {
                            let mixCom = self.combineVideos()
                            success(mixCom)
                            
                        })
                        
                    }
                }
            }
            
       
        }
    

    存储视频

    /**
         *  存储合成的视频
         *
         *  @param mixComposition mixComposition参数
         *  @param storeUrl       存储的路径
         *  @param successBlock   successBlock
         *  @param failureBlcok   failureBlcok
         */
        func store(_ mixComposition:AVMutableComposition,storeUrl:URL,success successBlock:@escaping ()->()){
            //weak var weakSelf = self
            var assetExport: AVAssetExportSession? = nil
            assetExport = AVAssetExportSession.init(asset: mixComposition, presetName: AVAssetExportPreset640x480)
            assetExport?.outputFileType = AVFileType("com.apple.quicktime-movie")
            assetExport?.outputURL = storeUrl
            assetExport?.exportAsynchronously(completionHandler: {
                successBlock()
               // UISaveVideoAtPathToSavedPhotosAlbum((storeUrl.path), self,#selector(weakSelf?.saveVideo(videoPath:didFinishSavingWithError:contextInfo:)), nil)
            })
            
        }
    

    好了,就到这里。

    相关文章

      网友评论

        本文标题:Live Photo实况照片的预录制设计开发思路

        本文链接:https://www.haomeiwen.com/subject/oongwftx.html