【问题标题】:Swift - How to record video in MP4 format with UIImagePickerController?Swift - 如何使用 UIImagePickerController 录制 MP4 格式的视频?
【发布时间】:2017-03-14 06:35:22
【问题描述】:

我正在创建一个应用程序,我需要在其中录制视频并将其上传到服务器。现在我的项目也有安卓版本了。要支持 android 版本,我必须以 mp4 格式录制视频。我按照this教程将UIImagePicker媒体类型设置为电影格式imagePicker.mediaTypes = [kUTTypeMovie as String]

UIImagePickerController 非常适合我的要求,我唯一需要更改的是其保存格式为 mp4。我在mediaTypes 中尝试了kUTTypeMPEG4,但它在运行时抛出错误,没有错误描述。

这是我的视频捕捉功能

func startCameraFromViewController() {

        if UIImagePickerController.isSourceTypeAvailable(.Camera) == false {
            return
        }
        viewBlack.hidden = false
        presentViewController(cameraController, animated: false, completion: nil)

        cameraController.sourceType = .Camera

        cameraController.mediaTypes = [kUTTypeMovie as String]
        //cameraController.mediaTypes = [kUTTypeMPEG4 as String]
        cameraController.cameraCaptureMode = .Video
        cameraController.videoQuality = .TypeMedium
        if(getPurchaseId() as! Int == 0)
        {
            if(txtBenchMark.text?.isEmpty == false)
            {
                cameraController.videoMaximumDuration = NSTimeInterval(300.0)
            }else{
                cameraController.videoMaximumDuration = NSTimeInterval(60.0)
            }
        }else{
            cameraController.videoMaximumDuration = NSTimeInterval(600.0)
        }
        cameraController.allowsEditing = false
    }

我正在使用带有Use Legacy swift Language version = Yes 的 Swift 2.2 和 Xcode 8

也欢迎任何替代解决方案。提前致谢。

编辑: 我发现没有办法直接快速录制mp4格式的视频。只能从苹果的quicktime mov格式转换成需要的格式。

【问题讨论】:

    标签: ios swift uiimagepickercontroller video-capture


    【解决方案1】:

    这里有一些代码可用于将录制的视频转换为 MP4:

    func encodeVideo(videoURL: NSURL)  {
    let avAsset = AVURLAsset(URL: videoURL, options: nil)
    
    var startDate = NSDate()
    
    //Create Export session
    exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)
    
    // exportSession = AVAssetExportSession(asset: composition, presetName: mp4Quality)
    //Creating temp path to save the converted video
    
    
    let documentsDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
    let myDocumentPath = NSURL(fileURLWithPath: documentsDirectory).URLByAppendingPathComponent("temp.mp4").absoluteString
    let url = NSURL(fileURLWithPath: myDocumentPath)
    
    let documentsDirectory2 = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as NSURL
    
    let filePath = documentsDirectory2.URLByAppendingPathComponent("rendered-Video.mp4")
    deleteFile(filePath)
    
    //Check if the file already exists then remove the previous file
    if NSFileManager.defaultManager().fileExistsAtPath(myDocumentPath) {
        do {
            try NSFileManager.defaultManager().removeItemAtPath(myDocumentPath)
        }
        catch let error {
            print(error)
        }
    }
    
     url
    
    exportSession!.outputURL = filePath
    exportSession!.outputFileType = AVFileTypeMPEG4
    exportSession!.shouldOptimizeForNetworkUse = true
    var start = CMTimeMakeWithSeconds(0.0, 0)
    var range = CMTimeRangeMake(start, avAsset.duration)
    exportSession.timeRange = range
    
    exportSession!.exportAsynchronouslyWithCompletionHandler({() -> Void in
        switch self.exportSession!.status {
        case .Failed:
            print("%@",self.exportSession?.error)
        case .Cancelled:
            print("Export canceled")
        case .Completed:
            //Video conversion finished
            var endDate = NSDate()
    
            var time = endDate.timeIntervalSinceDate(startDate)
            print(time)
            print("Successful!")
            print(self.exportSession.outputURL)
    
        default:
            break
        }
    
    })
    
    
    }
    
    func deleteFile(filePath:NSURL) {
    guard NSFileManager.defaultManager().fileExistsAtPath(filePath.path!) else {
        return
    }
    
    do {
        try NSFileManager.defaultManager().removeItemAtPath(filePath.path!)
    }catch{
        fatalError("Unable to delete file: \(error) : \(__FUNCTION__).")
    }
    }
    

    来源:https://stackoverflow.com/a/39329155/4786204

    【讨论】:

    • 感谢您的代码。我会试试看。但是你知道有什么方法可以直接将相机中的视频录制为 mp4 格式吗?
    • 我不相信 UI 图像选择器是可能的。
    • 感谢您的帮助朋友。我会试试上面的代码。
    • 感谢您的帮助,它运行良好。但是我需要根据我的需要和 swift 2.2 语法做一些更改
    【解决方案2】:

    我对以下 2 个答案进行了一些修改,使其与 Swift 5 兼容:
    https://stackoverflow.com/a/40354948/2470084
    https://stackoverflow.com/a/39329155/2470084

    import AVFoundation
    
    func encodeVideo(videoURL: URL){
        let avAsset = AVURLAsset(url: videoURL)
        let startDate = Date()
        let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)
        
        let docDir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        let myDocPath = NSURL(fileURLWithPath: docDir).appendingPathComponent("temp.mp4")?.absoluteString
        
        let docDir2 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as NSURL
        
        let filePath = docDir2.appendingPathComponent("rendered-Video.mp4")
        deleteFile(filePath!)
        
        if FileManager.default.fileExists(atPath: myDocPath!){
            do{
                try FileManager.default.removeItem(atPath: myDocPath!)
            }catch let error{
                print(error)
            }
        }
        
        exportSession?.outputURL = filePath
        exportSession?.outputFileType = AVFileType.mp4
        exportSession?.shouldOptimizeForNetworkUse = true
        
        let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
        let range = CMTimeRange(start: start, duration: avAsset.duration)
        exportSession?.timeRange = range
        
        exportSession!.exportAsynchronously{() -> Void in
            switch exportSession!.status{
            case .failed:
                print("\(exportSession!.error!)")
            case .cancelled:
                print("Export cancelled")
            case .completed:
                let endDate = Date()
                let time = endDate.timeIntervalSince(startDate)
                print(time)
                print("Successful")
                print(exportSession?.outputURL ?? "")
            default:
                break
            }
            
        }
    }
    
    func deleteFile(_ filePath:URL) {
        guard FileManager.default.fileExists(atPath: filePath.path) else{
            return
        }
        do {
            try FileManager.default.removeItem(atPath: filePath.path)
        }catch{
            fatalError("Unable to delete file: \(error) : \(#function).")
        }
    }
    

    【讨论】:

      【解决方案3】:

      在 iOS11 上运行,我们将始终收到 AVAssetExportSession 的 nil 值。对于这种情况,我们有什么解决方案吗?

      if let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) {
          //work on iOS 9 and 10
      } else {
          //always on iOS 11
      }
      

      【讨论】:

        【解决方案4】:

        快速swift 4更新之前的答案:

        func encodeVideo(videoUrl: URL, outputUrl: URL? = nil, resultClosure: @escaping (URL?) -> Void ) {
        
            var finalOutputUrl: URL? = outputUrl
        
            if finalOutputUrl == nil {
                var url = videoUrl
                url.deletePathExtension()
                url.appendPathExtension(".mp4")
                finalOutputUrl = url
            }
        
            if FileManager.default.fileExists(atPath: finalOutputUrl!.path) {
                print("Converted file already exists \(finalOutputUrl!.path)")
                resultClosure(finalOutputUrl)
                return
            }
        
            let asset = AVURLAsset(url: videoUrl)
            if let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetPassthrough) {
                exportSession.outputURL = finalOutputUrl!
                exportSession.outputFileType = AVFileType.mp4
                let start = CMTimeMakeWithSeconds(0.0, 0)
                let range = CMTimeRangeMake(start, asset.duration)
                exportSession.timeRange = range
                exportSession.shouldOptimizeForNetworkUse = true
                exportSession.exportAsynchronously() {
        
                    switch exportSession.status {
                    case .failed:
                        print("Export failed: \(exportSession.error != nil ? exportSession.error!.localizedDescription : "No Error Info")")
                    case .cancelled:
                        print("Export canceled")
                    case .completed:
                        resultClosure(finalOutputUrl!)
                    default:
                        break
                    }
                }
            } else {
                resultClosure(nil)
            }
        }
        

        【讨论】:

        • 非常感谢...但是视频无法在带有 HTML5 的 Chrome 上播放。有人有解决办法吗?
        • 此代码适用于较小的视频,但当我尝试压缩 791mb 文件时出现此错误。 videoMentorThreadCreateSampleBuffer 在 /BuildRoot/Library/Caches/com.apple.xbs/Sources/CoreMedia/CoreMedia-2501.27.4.3/Prototypes/FormatHandlers/VideoMentor.c:3798 发出 err=-12848 (err)(FigSampleGeneratorCreateSampleBufferAtCursor 失败)。压缩时错误发生在 78 -88% 左右。还有其他人遇到此错误吗?
        • 我一直失败操作无法完成
        【解决方案5】:

        对先前示例的小重构:

        import AVFoundation
        
        extension AVURLAsset {
            func exportVideo(presetName: String = AVAssetExportPresetHighestQuality,
                             outputFileType: AVFileType = .mp4,
                             fileExtension: String = "mp4",
                             then completion: @escaping (URL?) -> Void)
            {
                let filename = url.deletingPathExtension().appendingPathExtension(fileExtension).lastPathComponent
                let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename)
        
                if let session = AVAssetExportSession(asset: self, presetName: presetName) {
                    session.outputURL = outputURL
                    session.outputFileType = outputFileType
                    let start = CMTimeMakeWithSeconds(0.0, 0)
                    let range = CMTimeRangeMake(start, duration)
                    session.timeRange = range
                    session.shouldOptimizeForNetworkUse = true
                    session.exportAsynchronously {
                        switch session.status {
                        case .completed:
                            completion(outputURL)
                        case .cancelled:
                            debugPrint("Video export cancelled.")
                            completion(nil)
                        case .failed:
                            let errorMessage = session.error?.localizedDescription ?? "n/a"
                            debugPrint("Video export failed with error: \(errorMessage)")
                            completion(nil)
                        default:
                            break
                        }
                    }
                } else {
                    completion(nil)
                }
            }
        }
        

        另外:AVAssetExportPresetHighestQuality 预设在 Android / Chrome 上播放视频时有效。

        附:请注意,exportVideo 方法的完成处理程序可能不会在主线程上返回。

        【讨论】:

        • 视频文件无法在带有 HTML5 的 Chrome 上播放。你有什么想法吗?谢谢。
        【解决方案6】:

        Swift 5.2更新解决方案

        // Don't forget to import AVKit
        
        func encodeVideo(at videoURL: URL, completionHandler: ((URL?, Error?) -> Void)?)  {
            let avAsset = AVURLAsset(url: videoURL, options: nil)
        
            let startDate = Date()
        
            //Create Export session
            guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) else {
                completionHandler?(nil, nil)
                return
            }
        
            //Creating temp path to save the converted video
            let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
            let filePath = documentsDirectory.appendingPathComponent("rendered-Video.mp4")
        
            //Check if the file already exists then remove the previous file
            if FileManager.default.fileExists(atPath: filePath.path) {
                do {
                    try FileManager.default.removeItem(at: filePath)
                } catch {
                    completionHandler?(nil, error)
                }
            }
        
            exportSession.outputURL = filePath
            exportSession.outputFileType = AVFileType.mp4
            exportSession.shouldOptimizeForNetworkUse = true
              let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
            let range = CMTimeRangeMake(start: start, duration: avAsset.duration)
            exportSession.timeRange = range
        
            exportSession.exportAsynchronously(completionHandler: {() -> Void in
                switch exportSession.status {
                case .failed:
                    print(exportSession.error ?? "NO ERROR")
                    completionHandler?(nil, exportSession.error)
                case .cancelled:
                    print("Export canceled")
                    completionHandler?(nil, nil)
                case .completed:
                    //Video conversion finished
                    let endDate = Date()
        
                    let time = endDate.timeIntervalSince(startDate)
                    print(time)
                    print("Successful!")
                    print(exportSession.outputURL ?? "NO OUTPUT URL")
                    completionHandler?(exportSession.outputURL, nil)
        
                    default: break
                }
        
            })
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-06-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多