浏览代码

完成合成逻辑

harry 3 年之前
父节点
当前提交
95e5c1e7e6

+ 126 - 32
BFRecordScreenKit/Classes/BFRecordExport.swift.swift

@@ -8,48 +8,41 @@
 import Foundation
 import AVFoundation
 import BFFramework
+import BFVideoEditKit
+import Photos
+import GPUImage
 
 public class BFRecordExport {
     public var progress : ((Float)->Void)?
-    var count = 0
-    var timerr : Timer?
-    
-    var asset:AVAsset?
-    var voiceList:[PQVoiceModel]? {
+    public var exportCompletion : ((Error?, URL?)->Void)?
+    public var asset:AVURLAsset?
+    public var voiceList:[PQVoiceModel]? {
         didSet {
-            audioAsset = voiceList?.map({ model in
+            audioAssets = voiceList?.map({ model in
                 AVURLAsset(url: URL(fileURLWithPath: model.wavFilePath))
             })
             
         }
     }
-    var audioAsset : [AVURLAsset]?
+
+    var count = 0
+    
+    var audioAssets : [AVURLAsset]?
     
     var exporter : PQCompositionExporter?
     var mStickers = [PQEditVisionTrackMaterialsModel]()
     
     deinit {
-        timerr?.invalidate()
-        timerr = nil
     }
     public init(){}
 
     
     //MARK: -
     public func start(){
-        guard timerr == nil else {
-            return
-        }
-
-        timerr = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
-        RunLoop.current.add(timerr!, forMode: .common)
-        timerr?.fire()
-        
-        
         // 1,背景视频素材
         let bgMovieInfo: PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel()
         bgMovieInfo.type = StickerType.VIDEO.rawValue
-        bgMovieInfo.locationPath = (asset as? AVURLAsset)?.url.absoluteString ?? ""
+        bgMovieInfo.locationPath = (asset?.url.absoluteString)?.removingPercentEncoding ?? ""
         bgMovieInfo.timelineIn = 0
         bgMovieInfo.timelineOut = CMTimeGetSeconds(asset?.duration ?? CMTime.zero)
         bgMovieInfo.model_in = bgMovieInfo.timelineIn
@@ -57,12 +50,123 @@ public class BFRecordExport {
         bgMovieInfo.canvasFillType = stickerContentMode.aspectFitStr.rawValue
         mStickers.append(bgMovieInfo)
     
-        beginExport(videoStickers: mStickers, audioAsset: [(self.asset as! AVURLAsset)])
+        beginExport(videoStickers: mStickers, audioAsset: self.audioAssets)
     }
     
-    func beginExport(videoStickers:[PQEditVisionTrackMaterialsModel], audioAsset: [AVURLAsset]) {
- 
+    enum DispatchError: Error {
+        case timeout
+    }
+
+    
+    func beginExport(videoStickers:[PQEditVisionTrackMaterialsModel], audioAsset: [AVURLAsset]?) {
+        // 输出视频地址
+        var outPutMP4Path = exportVideosDirectory
+        if !directoryIsExists(dicPath: outPutMP4Path) {
+            createDirectory(path: outPutMP4Path)
+        }
+        outPutMP4Path.append("video_\(String.qe.timestamp()).mp4")
+        let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
+        BFLog(1, message: "导出视频地址 \(outPutMP4URL)")
         
+        // 处理导出
+        if (audioAsset?.count ?? 0 ) > 0 || videoStickers.count > 1 {
+            var audioUrl:URL?
+            if audioAsset?.count ?? 0 > 0 {
+                // 多音频合成
+                if let list = voiceList?.map({ model in
+                    URL(fileURLWithPath: model.wavFilePath)
+                }){
+                    if list.count == 1 {
+                        audioUrl = list.first
+                    }else {
+                        let semaphore = DispatchSemaphore(value: 0)
+                        PQPlayerViewModel.mergeAudios(urls: list) { completURL in
+                            audioUrl = completURL
+                            BFLog(1, message: "异步做同步")
+                            semaphore.signal()
+                        }
+                        _ = semaphore.wait(timeout: .now() + 5)
+                    }
+                }
+            }
+            // 每次初始化的时候设置初始值 为 nIl
+            var audioMix: AVMutableAudioMix?
+            var composition: AVMutableComposition?
+
+            let filter = mStickers.map { sticker in
+                PQMovieFilter(movieSticker: sticker)
+            }
+            // 有
+            if let completURL = audioUrl {
+                let inputAsset = AVURLAsset(url: completURL, options: avAssertOptions)
+                (audioMix, composition) = PQVideoEditViewModel.setupAudioMix(originAsset: inputAsset, bgmData: nil, videoStickers: videoStickers)
+                
+                if composition != nil {
+                    exporter = PQCompositionExporter(asset: composition!, videoComposition: nil, audioMix: audioMix, filters: filter, animationTool: nil, exportURL: outPutMP4URL)
+                }else {
+                    exporter = PQCompositionExporter(asset: inputAsset, videoComposition: nil, audioMix: nil, filters: filter, animationTool: nil, exportURL: outPutMP4URL)
+                }
+            }
+            
+            let size = getVideoSize()
+            var orgeBitRate = Int(size.width * size.height * 3)
+            
+            for stick in mStickers {
+                if stick.type == StickerType.VIDEO.rawValue {
+                    let asset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + stick.locationPath), options: avAssertOptions)
+                    
+                    let cbr = asset.tracks(withMediaType: .video).first?.estimatedDataRate
+                    if Int(cbr ?? 0) > orgeBitRate {
+                        orgeBitRate = Int(cbr ?? 0)
+                    }
+                }
+            }
+            
+            BFLog(message: "导出设置的码率为:\(orgeBitRate)")
+            if exporter!.prepare(videoSize: size, videoAverageBitRate: orgeBitRate) {
+                exporter!.start(playeTimeRange: CMTimeRange(start: CMTime.zero, end: asset?.duration ?? CMTime.zero))
+            }
+            exporter?.progressClosure = { [weak self] _, _, progress in
+                //            BFLog(message: "正片合成进度 \(progress * 100)%")
+                let useProgress = progress > 1 ? 1 : progress
+                if progress > 0 { // 更新进度
+                    self?.progress?(useProgress)
+                }
+            }
+            exporter?.completion = { [weak self] url in
+                // 输出视频时长
+                if let url = url {
+                    let outSeconds = CMTimeGetSeconds(AVAsset(url: url).duration)
+                    
+                    BFLog(1, message: "无水印的视频导出完成: \(String(describing: url)) 生成视频时长为:\(outSeconds)")
+                    cShowHUB(superView: nil, msg: ( outSeconds == 0) ? "合成失败请重试。" : "合成成功")
+                    self?.saveVideoToPhoto(url: url)
+                    self?.exportCompletion?(nil, url)
+
+                }else{
+                    let error = NSError(domain: "err", code: -1, userInfo: nil)
+                    self?.exportCompletion?(error as Error, nil)
+                    
+                }
+                
+                // 导出完成后取消导出
+                self?.exporter?.cancel()
+            }
+        } else {
+            // 没有处理,直接copy原文件
+            self.exportCompletion?(nil, self.asset?.url)
+        }
+
+    }
+    
+    func saveVideoToPhoto(url:URL){
+        PHPhotoLibrary.shared().performChanges {
+            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
+        } completionHandler: { isFinished, _ in
+            DispatchQueue.main.async {
+                cShowHUB(superView: nil, msg: "保存成功")
+            }
+        }
     }
     
     func dealAsset(){
@@ -73,16 +177,6 @@ public class BFRecordExport {
 //        })
     }
     
-    @objc func updateCounter() {
-        if self.count == 100 {
-            timerr?.invalidate()
-            timerr = nil
-            return
-        }
-        self.count += 1
-        self.progress?(Float(self.count)/100.0)
-    }
-    
     func getVideoSize() -> CGSize{
         var size = CGSize.zero
         self.asset?.tracks.forEach({ track in

+ 1 - 1
BFRecordScreenKit/Classes/BFRecordManager.swift

@@ -112,7 +112,7 @@ class BFRecordManager {
 
                 // 处理降噪
                 let noiseFilePath = url.replacingOccurrences(of: ".wav", with: "_noise_\(1)_.wav")
-                BFLog(message: "降噪后地址:\(noiseFilePath)")
+                BFLog(1, message: "降噪后地址:\(noiseFilePath)")
                 NXNoiseReduction().denoise(url, outFile: noiseFilePath)
                 let model = PQVoiceModel()
                 model.wavFilePath = noiseFilePath

+ 10 - 5
BFRecordScreenKit/Classes/BFRecordScreenController.swift

@@ -18,7 +18,9 @@ public class BFRecordScreenController: BFBaseViewController {
     public var nextActionHandle:(()->Void)?
     public var closeActionHandle:(()->Void)?
     public var asset:PHAsset?
-    
+    public var avasset:AVURLAsset?
+    public var recordList:[PQVoiceModel] = [PQVoiceModel]()
+
     var assetPlayer:AVPlayer?       // 原视频音频播放器
     lazy var recordPlayer:AVAudioPlayer = {// 录音音频播放器
         let player = AVAudioPlayer()
@@ -68,7 +70,6 @@ public class BFRecordScreenController: BFBaseViewController {
     var beginOnStartBtn:Bool = false
     var touchStart:CGPoint = CGPoint(x: 0, y: 0)
     var avplayerTimeObserver: NSKeyValueObservation?
-    var recordList:[PQVoiceModel] = [PQVoiceModel]()
 
     lazy var playBtn:UIButton = {
         let btn = UIButton(frame: view.bounds)
@@ -435,7 +436,11 @@ public class BFRecordScreenController: BFBaseViewController {
 //            }else{
 //
 //            }
-
+            PHCachingImageManager().requestAVAsset(forVideo: asset, options: nil, resultHandler: {[weak self] (asset: AVAsset?, audioMix: AVAudioMix?, info) in
+                if let urlass = asset as? AVURLAsset {
+                    self?.avasset = urlass
+                }
+            })
         }
 
     }
@@ -490,6 +495,8 @@ public class BFRecordScreenController: BFBaseViewController {
         movie?.removeAllTargets()
     }
     
+    //MARK: - 录音对应图像绘制
+    
     func changeProgress(progress:Float) {
         if let item = assetPlayer?.currentItem {
             let duration = CMTimeGetSeconds(item.duration)
@@ -501,8 +508,6 @@ public class BFRecordScreenController: BFBaseViewController {
         }
     }
     
-    //MARK: - 录音对应图像绘制
-    
     func drewRecordProgessLable(){
         
     }

+ 2 - 0
Example/BFRecordScreenKit/IntroduceController.swift

@@ -23,6 +23,8 @@ class IntroduceController : BFBaseViewController {
         vc.nextActionHandle = {
             DispatchQueue.main.async { [weak self] in
                 let controller = VideoExportController()
+                controller.export.voiceList = vc.recordList
+                controller.export.asset = vc.avasset
                 self?.navigationController?.pushViewController(controller, animated: true)
             }
         }

+ 45 - 15
Example/BFRecordScreenKit/VideoExportController.swift

@@ -10,31 +10,61 @@ import Foundation
 import BFRecordScreenKit
 import BFUIKit
 import UIKit
+import AVFoundation
 
 class VideoExportController: BFBaseViewController{
     
+    var videoAsset : AVURLAsset?
+    
+    lazy var progressView : UIView = {
+        let v = UIView(frame: CGRect(x: 10, y: 100, width: 0, height: 30))
+        v.backgroundColor = .red
+        return v
+    }()
+    
+    lazy var progressL : UILabel = {
+        let la = UILabel(frame: CGRect(x: 10, y: 100, width: cScreenWidth - 20, height: 30))
+        la.textColor = .white
+        la.text = "0%"
+        la.font = UIFont.systemFont(ofSize: 18)
+        return la
+    }()
+    
+    lazy var export : BFRecordExport = {
+        let export = BFRecordExport()
+        
+        export.progress = {[weak self] progress in
+            self?.progressView.frame = CGRect(x: 10, y: 100, width: (cScreenWidth - 20) * CGFloat(progress), height: 30)
+            self?.progressL.text = String(format: "%d", Int(progress*100))
+        }
+        export.exportCompletion = {[weak self] (error, url) in
+            guard let strongSelf = self else {
+                return
+            }
+            
+            if let fileUrl = url {
+                DispatchQueue.main.async {
+                    let item = AVPlayerItem(url: fileUrl)
+                    let avplayer = AVPlayer(playerItem: item)
+                    let playerLayer = AVPlayerLayer(player: avplayer)
+                    playerLayer.frame = CGRect(x: 10, y: strongSelf.progressView.bottomY+5, width: cScreenWidth - 20, height: cScreenHeigth - strongSelf.progressView.bottomY - 10)
+                    strongSelf.view.layer.addSublayer(playerLayer)
+                    avplayer.play()
+                }
+            }
+        }
+        return export
+    }()
+
     override func viewDidLoad() {
         super.viewDidLoad()
         
         let backV = UIView(frame: CGRect(x: 10, y: 100, width: cScreenWidth - 20, height: 30))
         backV.backgroundColor = .gray
         view.addSubview(backV)
-
-        let progressView = UIView(frame: CGRect(x: 10, y: 100, width: 0, height: 30))
-        progressView.backgroundColor = .red
         view.addSubview(progressView)
-        
-        let la = UILabel(frame: backV.frame)
-        la.textColor = .white
-        la.text = "0%"
-        la.font = UIFont.systemFont(ofSize: 18)
-        view.addSubview(la)
-        
-        let export = BFRecordExport()
-        export.progress = { progress in
-            progressView.frame = CGRect(x: 10, y: 100, width: (cScreenWidth - 20) * CGFloat(progress), height: 30)
-            la.text = String(format: "%d", Int(progress*100))
-        }
+        view.addSubview(progressL)
+
         export.start()
     }
 }

+ 4 - 4
Example/Podfile.lock

@@ -38,7 +38,7 @@ PODS:
     - LMJHorizontalScrollText (= 2.0.2)
     - MJRefresh (= 3.7.2)
     - ObjectMapper (= 4.2.0)
-    - TXLiteAVSDK_Player (= 9.2.10637)
+    - TXLiteAVSDK_Player (= 9.3.10765)
     - WechatOpenSDK-Swift (= 1.8.7.1)
   - BFMaterialKit (0.1.9):
     - BFUIKit
@@ -125,7 +125,7 @@ PODS:
   - SnapKit (5.0.1)
   - SVProgressHUD (2.2.5)
   - Toast-Swift (5.0.1)
-  - TXLiteAVSDK_Player (9.2.10637)
+  - TXLiteAVSDK_Player (9.3.10765)
   - WechatOpenSDK-Swift (1.8.7.1)
 
 DEPENDENCIES:
@@ -184,7 +184,7 @@ SPEC CHECKSUMS:
   AliyunOSSiOS: b8f1dfc229cd9abf68c8ee0cb245c2d66e00dd96
   BFAliyunNlsSDK-Swift: 44049d173720cf858729d3b011c07e0c33c90fd2
   BFCommonKit: fbebd7d46eaa7adaf5311aae2230b68ab5e99788
-  BFFramework: f77b349fadfcee2ca279d7826b06f65d9e06e67f
+  BFFramework: 2c44a33844e1a737c4f581b5c4949a9298867e5e
   BFMaterialKit: a10f33e7748689a3eeffff3b18df9c350241ba8d
   BFNetRequestKit: 6b200205bd1a9491c04f5a3e95301d37a547f96b
   BFRecordScreenKit: ebe9e2888a1a139274c807cf28171ca8112da9a4
@@ -206,7 +206,7 @@ SPEC CHECKSUMS:
   SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
   SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
   Toast-Swift: 9b6a70f28b3bf0b96c40d46c0c4b9d6639846711
-  TXLiteAVSDK_Player: 300e6fc7262ae095ee13b18d7d821c5fae0996f9
+  TXLiteAVSDK_Player: 2b60edf893a8e82165a5e4b961a6cb347b10be4a
   WechatOpenSDK-Swift: 18a8f7b12e745c30acc013f72a9f8a25aad6e216
 
 PODFILE CHECKSUM: fecae16510aa8ec73993940aae423db47aa4af21