美文网首页日常收录
【iOS】视频录像相关功能调研(二)

【iOS】视频录像相关功能调研(二)

作者: 在这蓝色天空下 | 来源:发表于2021-05-21 11:19 被阅读0次

    @[toc]

    1、获取视频时长(秒数)

        //MARK: 获取视频时长(秒数)
        @objc func getVideoLength() {
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true )
            let documentsDirectory = paths[0]  as  String
            let videoPath = documentsDirectory + "/" + "1619172935.mp4"
            
            if !FileManager.default.fileExists(atPath: videoPath) {
                print("文件不存在,请先拍照,再修改视频地址")
                return
            }
            
            let avUrlAsset = AVURLAsset.init(url: URL(fileURLWithPath: videoPath))
            let cmtime = avUrlAsset.duration
            let second = Int(cmtime.seconds)
            
            print("视频秒数 == \(second)")
        }
    

    2、获取视频文件大小

        //MARK: 获取视频文件大小//文件属性
        @objc func getVideoSize() {
            
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true )
            let documentsDirectory = paths[0]  as  String
            let videoPath = documentsDirectory + "/" + "1619172935.mp4"
    
            if !FileManager.default.fileExists(atPath: videoPath) {
                print("文件不存在,请先拍照,再修改视频地址")
                return
            }
            
            let fileManager = FileManager.default
            if fileManager.fileExists(atPath: videoPath) {
                let fileDic = try! fileManager.attributesOfItem(atPath: videoPath)
                let size = fileDic[FileAttributeKey(rawValue: "NSFileSize")] as? Int ?? 0
                print("\(size)B")
                print("\(size/1024)KB")
                let sizeM = String(format: "%.2f", Float(size)/1024/1024)
                print(sizeM + "M")
                
            }else{
                print("文件不存在")
            }
        }
    

    3、获取指定时间帧图片

        //MARK: 获取指定时间帧图片
        
        /// 获取指定时间帧图片
        /// - Parameters:
        ///   - videoUrl: 视频地址
        ///   - cmtime: 指定的时间
        ///   - width: 宽度 根据视频的宽高比来计算图片的高度
        @objc func getImage() {
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true )
            let documentsDirectory = paths[0]  as  String
            let videoPath = documentsDirectory + "/" + "1619172935.mp4"
            
            if !FileManager.default.fileExists(atPath: videoPath) {
                print("文件不存在,请先拍照,再修改视频地址")
                return
            }
            
            let cmtime = CMTimeMake(value: 1, timescale: 10)
            let width = 300
            
            // 获取指定时间的帧图片
            DispatchQueue.global().async {
                //建立新的AVAsset & AVAssetImageGenerator
                let asset = AVAsset.init(url: URL(fileURLWithPath: videoPath))
                let imageGenerator = AVAssetImageGenerator.init(asset: asset)
                //设置maximumSize 宽为100,高为0 根据视频的宽高比来计算图片的高度 控制图片清晰度
                imageGenerator.maximumSize = CGSize(width: width, height: 0)
                //捕捉视频缩略图会考虑视频的变化(如视频的方向变化),如果不设置,缩略图的方向可能出错
                imageGenerator.appliesPreferredTrackTransform = true
                // CMTimeMake第一个参数是时间,第二个参数是 每秒的分数 第一个/第二个 才是秒
                let imageRef = try! imageGenerator.copyCGImage(at:cmtime, actualTime: nil)
                //将图片转化为UIImage
                let image = UIImage.init(cgImage: imageRef)
                DispatchQueue.main.async {
                    //保存到相册
                    self.saveImage(image: image)
                }
            }
        }
    

    4、多视频合成

    
        @objc func starMerge() {
            let videoPaths = [
                "1619163716.mp4",
                "1619163734.mp4",
                "1619163741.mp4"
            ]
            
            for i in 0..<videoPaths.count {
                if !FileManager.default.fileExists(atPath: videoPaths[i]) {
                    print("文件不存在,请先拍照,再修改视频地址")
                    return
                }
            }
       
            let outputPath = self.getNewPath(videoTyle: AVFileType.mp4)
            self.mergeVideo(videoPaths: videoPaths, outputPath: outputPath) { (success) in
                if success {
                    print("多视频合成 成功")
                }else{
                    print("多视频合成 失败")
                }
            }
        }
        
        //MARK: 多视频合成
        func mergeVideo(videoPaths:[String], outputPath:String, completeHandler:@escaping (Bool)->()) {
            if videoPaths.count < 2 {
                return
            }
            
            let mixComposition = AVMutableComposition()
            //音频轨道
            let audioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
            
            //视频轨道
            let videoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
            
            var totalDuration = CMTime.zero
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true )
            let documentsDirectory = paths[0]  as  String
            
            for i in 0 ..< videoPaths.count {
                let pathUrl = documentsDirectory + "/\(videoPaths[i])"
                if !FileManager.default.fileExists(atPath: pathUrl) {
                    print("文件不存在")
                    break
                }
                
                let asset = AVURLAsset.init(url: URL(fileURLWithPath: pathUrl))
                // 获取AVAsset中的音频
                let assetAudioTracks = asset.tracks(withMediaType: AVMediaType.audio)
                if assetAudioTracks.count == 0 {
                    print("未获取到音频")
                    break
                }
                // 向通道内加入音频
                try! audioTrack?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: asset.duration), of: assetAudioTracks.first!, at: totalDuration)
                
                // 获取AVAsset中的视频
                let assetVideoTracks = asset.tracks(withMediaType: AVMediaType.video)
                if assetVideoTracks.count == 0 {
                    print("未获取到视频")
                    break
                }
                // 向通道内加入视频
                try! videoTrack?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: asset.duration), of: assetVideoTracks.first!, at: totalDuration)
                totalDuration = CMTimeAdd(totalDuration, asset.duration)
            }
            
            // 导出合成后的视频
            let outputURL = URL(fileURLWithPath: outputPath)
            let avAssetExportSession = AVAssetExportSession.init(asset: mixComposition, presetName: AVAssetExportPresetMediumQuality)
            avAssetExportSession?.outputURL = outputURL
            avAssetExportSession?.outputFileType = .mp4
            avAssetExportSession?.shouldOptimizeForNetworkUse = true
            avAssetExportSession?.exportAsynchronously {
                switch avAssetExportSession?.status {
                
                case .unknown:
                    print("AVAssetExportSessionStatusUnknown")
                    break
                case .waiting:
                    print("AVAssetExportSessionStatusWaiting")
                    break
                case .exporting:
                    print("AVAssetExportSessionStatusExporting")
                    break
                case .completed:
                    print("AVAssetExportSessionStatusCompleted")
                    self.getVideoSize(videoUrl: outputURL)
                    self.getVideoLength(videoUrl: outputURL)
                    completeHandler(true)
                    break
                case .failed:
                    print("AVAssetExportSessionStatusFailed")
                    completeHandler(false)
                    break
                case .cancelled:
                    print("AVAssetExportSessionStatusCancelled")
                    completeHandler(false)
                    break
    
                default:
                    break
                }
            }
        }
    
    

    5、视频压缩/转码

        
        @objc func starConvert() {
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true )
            let documentsDirectory = paths[0]  as  String
            let videoPath = documentsDirectory + "/" + "1619331826.mp4"
            
            
            if !FileManager.default.fileExists(atPath: videoPath) {
                print("文件不存在,请先拍照,再修改视频地址")
                return
            }
    
            let outputPath = self.getNewPath(videoTyle: AVFileType.mov)
            
            self.convertVideo(inputURL: URL(fileURLWithPath: videoPath),
                              outputURL: URL(fileURLWithPath: outputPath),
                              presetName: AVAssetExportPresetMediumQuality) { (success) in
                if success {
                    print("压缩/转码 成功")
                }else{
                    print("压缩/转码 失败")
                }
            }
        }
    
        //MARK: 视频压缩//转换格式
        
        /// 视频压缩//转换格式
        /// - Parameters:
        ///   - inputURL: 视频地址
        ///   - outputURL: 视频压缩后的地址
        ///   - presetName: 视频预设
        ///   - completeHandler: <#completeHandler description#>
        /// - Returns: <#description#>
        func convertVideo(inputURL:URL, outputURL:URL, presetName:String = AVAssetExportPresetMediumQuality, completeHandler:@escaping (Bool)->()) {
            let avAsset = AVURLAsset.init(url: inputURL)
            let avAssetExportSession = AVAssetExportSession.init(asset: avAsset, presetName: presetName)
            avAssetExportSession?.outputURL = outputURL
            avAssetExportSession?.outputFileType = .mp4
            avAssetExportSession?.shouldOptimizeForNetworkUse = true
            avAssetExportSession?.exportAsynchronously {
                switch avAssetExportSession?.status {
                
                case .unknown:
                    print("AVAssetExportSessionStatusUnknown")
                    break
                case .waiting:
                    print("AVAssetExportSessionStatusWaiting")
                    break
                case .exporting:
                    print("AVAssetExportSessionStatusExporting")
                    break
                case .completed:
                    print("AVAssetExportSessionStatusCompleted")
                    self.getVideoSize(videoUrl: outputURL)
                    self.getVideoLength(videoUrl: outputURL)
                    completeHandler(true)
                    break
                case .failed:
                    print("AVAssetExportSessionStatusFailed")
                    completeHandler(false)
                    break
                case .cancelled:
                    print("AVAssetExportSessionStatusCancelled")
                    completeHandler(false)
                    break
                default:
                    break
                }
            }
        }
    

    6、添加水印(图片、文字)

        @objc func starAddImage() {
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true )
            let documentsDirectory = paths[0]  as  String
            let videoPath = documentsDirectory + "/" + "1619343879.mp4"
            
            
            if !FileManager.default.fileExists(atPath: videoPath) {
                print("文件不存在,请先拍照,再修改视频地址")
                return
            }
            
            
            let outputPath = self.getNewPath(videoTyle: AVFileType.mp4)
            print(outputPath)
    
            self.videoAddMark(imageName: "good", title: nil, inputPath: videoPath, outputPath: outputPath) { (success) in
                if success {
                    print("添加水印 成功")
                }else{
                    print("添加水印 失败")
                }
            }
        }
        
        @objc func starAddTitle() {
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true )
            let documentsDirectory = paths[0]  as  String
            let videoPath = documentsDirectory + "/" + "1619343879.mp4"
            
            
            if !FileManager.default.fileExists(atPath: videoPath) {
                print("文件不存在,请先拍照,再修改视频地址")
                return
            }
            
            
            let outputPath = self.getNewPath(videoTyle: AVFileType.mp4)
            print(outputPath)
            self.videoAddMark(imageName: nil, title: "啦啦啦", inputPath: videoPath, outputPath: outputPath) { (success) in
                if success {
                    print("添加水印 成功")
                }else{
                    print("添加水印 失败")
                }
            }
        }
        
        //添加水印
        func videoAddMark(imageName:String?, title:String?, inputPath:String, outputPath:String, completeHandler:@escaping (Bool)->()) {
            
            //创建AVAsset实例
            let videoAsset = AVURLAsset.init(url: URL(fileURLWithPath: inputPath))
            
            let mixComposition = AVMutableComposition()
            //音频轨道
            let audioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
            // 获取AVAsset中的音频
            let assetAudioTracks = videoAsset.tracks(withMediaType: AVMediaType.audio)
            if assetAudioTracks.count == 0 {
                print("未获取到音频")
                return
            }
            // 向通道内加入音频
            try! audioTrack?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration), of: assetAudioTracks.first!, at: CMTime.zero)
            
            //视频轨道
            let videoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
            // 获取AVAsset中的视频
            let assetVideoTracks = videoAsset.tracks(withMediaType: AVMediaType.video)
            if assetVideoTracks.count == 0 {
                print("未获取到视频")
                return
            }
            // 向通道内加入视频
            try! videoTrack?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration), of: assetVideoTracks.first!, at: CMTime.zero)
            
            //1 AVMutableVideoCompositionInstruction 视频轨道中的一个视频,可以缩放、旋转等
            let mainInstruction = AVMutableVideoCompositionInstruction()
            mainInstruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration)
            
            // 2 AVMutableVideoCompositionLayerInstruction 一个视频轨道,包含了这个轨道上的所有视频素材
            let videolayerInstruction = AVMutableVideoCompositionLayerInstruction.init(assetTrack: videoTrack!)
    
            let videoTransform = (assetVideoTracks.first?.preferredTransform)!
            var isVideoAssetPortrait = true
            var naturalSize = (assetVideoTracks.first?.naturalSize)!
    
            if videoTransform.a == 0,
               videoTransform.b == 1,
               videoTransform.c == -1,
               videoTransform.d == 0
               {
                isVideoAssetPortrait = true
                
            } else if videoTransform.a == 0,
                     videoTransform.b == -1,
                     videoTransform.c == 1,
                     videoTransform.d == 0 {
                isVideoAssetPortrait = true
            } else if videoTransform.a == 1,
                      videoTransform.b == 0,
                      videoTransform.c == 0,
                      videoTransform.d == 1 {
                 isVideoAssetPortrait = false
             } else if videoTransform.a == -1,
                       videoTransform.b == 0,
                       videoTransform.c == 0,
                       videoTransform.d == -1 {
                  isVideoAssetPortrait = false
              }
            videolayerInstruction.setTransform(videoTransform, at: CMTime.zero)
            
            // 3 - Add instructions
            mainInstruction.layerInstructions = [videolayerInstruction]
    
            //AVMutableVideoComposition:管理所有视频轨道,水印添加就在这上面
            let mainCompositionInst = AVMutableVideoComposition()
            
            if isVideoAssetPortrait {
                naturalSize = CGSize(width: naturalSize.height, height: naturalSize.width)
            }
            let width = naturalSize.width
            let height = naturalSize.height
            mainCompositionInst.renderSize = CGSize.init(width: width, height: height)
            mainCompositionInst.instructions = [mainInstruction]
            mainCompositionInst.frameDuration = CMTimeMake(value: 1, timescale: 30)
                    
            if title == nil && (imageName == nil || UIImage(named: imageName!) == nil) {
                completeHandler(false)
                return
            }
            
            let overlayLayer = CALayer()
    
            if title != nil {
                // 文字
                let subtitle1Text = CATextLayer()
                subtitle1Text.font = "Helvetica-Bold" as CFTypeRef
                subtitle1Text.fontSize = 40
                subtitle1Text.frame = CGRect(x: width/2-100, y: height/2-60, width: 200, height: 120)
                subtitle1Text.string = title
                subtitle1Text.alignmentMode = .center
                subtitle1Text.foregroundColor = UIColor.white.cgColor
                overlayLayer.addSublayer(subtitle1Text)
            }
            if imageName != nil, let image = UIImage(named: imageName!) {
                //图片
                let picLayer = CALayer()
                picLayer.contents = image.cgImage
                picLayer.frame = CGRect(x: width/2-80, y: height/2-80, width: 160, height: 160)
                overlayLayer.addSublayer(picLayer)
            }
            
            overlayLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
            overlayLayer.masksToBounds = true
            
            let parentLayer = CALayer()
            let videoLayer = CALayer()
            parentLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
            videoLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
            parentLayer.addSublayer(videoLayer)
            parentLayer.addSublayer(overlayLayer)
            mainCompositionInst.animationTool = AVVideoCompositionCoreAnimationTool.init(postProcessingAsVideoLayer: videoLayer, in: parentLayer)
            
            // 导出合成后的视频
            let outputURL = URL(fileURLWithPath: outputPath)
            let avAssetExportSession = AVAssetExportSession.init(asset: mixComposition, presetName: AVAssetExportPresetMediumQuality)
            avAssetExportSession?.outputURL = outputURL
            avAssetExportSession?.outputFileType = .mp4
            avAssetExportSession?.shouldOptimizeForNetworkUse = true
            avAssetExportSession?.videoComposition = mainCompositionInst;
    
            avAssetExportSession?.exportAsynchronously {
                switch avAssetExportSession?.status {
                
                case .unknown:
                    print("AVAssetExportSessionStatusUnknown")
                    break
                case .waiting:
                    print("AVAssetExportSessionStatusWaiting")
                    break
                case .exporting:
                    print("AVAssetExportSessionStatusExporting")
                    break
                case .completed:
                    print("AVAssetExportSessionStatusCompleted")
                    self.getVideoSize(videoUrl: outputURL)
                    self.getVideoLength(videoUrl: outputURL)
                    completeHandler(true)
                    break
                case .failed:
                    print("AVAssetExportSessionStatusFailed")
                    completeHandler(false)
                    break
                case .cancelled:
                    print("AVAssetExportSessionStatusCancelled")
                    completeHandler(false)
                    break
                default:
                    break
                }
            }
        }
    

    7、获取一个新的沙盒存储地址

        //MARK: 获取一个新的沙盒存储地址
        /// 获取一个新的沙盒存储地址
        /// - Returns: <#description#>
        func getNewPath(videoTyle: AVFileType) -> String {
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true )
            let documentsDirectory = paths[0]  as  String
            let timeInterval = Int(Date().timeIntervalSince1970)
            var filePath = "\(documentsDirectory)/\(timeInterval)"
    
            switch videoTyle {
            case .mp4:
                filePath = filePath + ".mp4"
                break
            case .mov:
                filePath = filePath + ".mov"
                break
            default:
                filePath = filePath + ".mp4"
                break
            }
            
            return filePath
        }
    

    8、根据路径删除沙盒中某个文件

        //MARK: 根据路径删除沙盒中某个文件
        func deleteFile(path:String) -> Bool {
            if FileManager.default.fileExists(atPath: path) {
                do {
                    try FileManager.default.removeItem(atPath: path)
                    return true
                } catch  {
                    return false
                }
            }
            return false
        }
    

    9、保存图片到相册

        //MARK: 保存图片到相册
        func saveImage(image: UIImage) {
            UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.saveImage(image:didFinishSavingWithError:contextInfo:)), nil)
        }
        
        @objc private func saveImage(image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: AnyObject) {
            var info = ""
            if error != nil{
                info = "保存图片失败"
            }else{
                info = "保存图片成功"
            }
            print(info)
        }
    

    10、保存视频到相册

        //MARK:保存视频到相册
        func saveVideoToAlbum(videoUrl: URL) {
            var info = ""
            PHPhotoLibrary.shared().performChanges({
                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoUrl)
            }) { (success, error) in
                if success {
                    info = "保存成功"
                } else {
                    info = "保存失败,err = \(error.debugDescription)"
                }
                
                DispatchQueue.main.async {
                    let alertVC = UIAlertController(title: info, message: nil, preferredStyle: .alert)
                    alertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                    self.present(alertVC, animated: true, completion: nil)
                }
            }
        }
    

    相关文章

      网友评论

        本文标题:【iOS】视频录像相关功能调研(二)

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