如何从 UIImagePickerController 将 iOS 视频编码为 .mp4,以便 Android 设备可以播放它们?

How do I encode iOS videos as .mp4 from UIImagePickerController so that Android devices can play them?

我正在使用 UIImagePickerController 录制短视频(<30 秒),然后通过我们的 API 保存和上传这些视频。该应用程序是跨平台的,因此我需要将录制的视频编码为 mp4 格式,以便 Android 设备可以播放它们。

我使用了以下问题的说明来创建我的解决方案:

AVFoundation record video in MP4 format

https://forums.developer.apple.com/thread/94762

我像这样通过 UIImagePickerController 录制我的视频:

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    // Local variable inserted by Swift 4.2 migrator.
    let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)


    let videoNSURL = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaURL)] as? NSURL

    videoURL = videoNSURL!.absoluteURL
    if let videoURL = videoURL {
        let avAsset = AVURLAsset(url: videoURL, options: nil)

        avAsset.exportVideo { (exportedURL) in
            if let uploadVC = self.uploadVC {
                uploadVC.incomingFileURL = exportedURL
                uploadVC.myJewelleryID = self.myJewelleryID
                uploadVC.topicID = self.topicID
            }
            DispatchQueue.main.async { [weak self] in
              //Update UI with results from previous closure
                self?.dismiss(animated: true, completion: nil)
                self?.showUploadContainer()
                self?.updateVideoContainerWithURL(url: exportedURL)
            }
        }
    }
}

然后将导出的 MP4 url 传递到上传容器视图,将文件保存到设备:

private func saveVideoFileToDevice() {

    //Filename Struct = [AssetID]_[TopicID]_[CustomerID]_[Datestamp]
    let date = Date()
    let formater = DateFormatter()
    formater.locale = Locale(identifier: "en_US_POSIX")
    formater.dateFormat = "YYYY-MM-dd-HH-mm-ss"

    uploadFileName = ""
    if let mjID = myJewelleryID {
        uploadFileName = "ASID_\(mjID)_\(User.instance.customerID)_\(formater.string(from: date)).mp4"
    } else if let tID = topicID {
        uploadFileName = "ASID_\(tID)_\(User.instance.customerID)_\(formater.string(from: date)).mp4"
    }

    let fileManager = FileManager.default

    if let destURL = URL(string: "file://\(NSHomeDirectory())/Documents/\(uploadFileName!)") {

        var fileData: Data!
        print("destURL = \(destURL)")
        do {
            try fileManager.copyItem(at: incomingFileURL! as URL, to: destURL)
            fileData = try Data(contentsOf: incomingFileURL! as URL)

            try fileData.write(to: destURL)


        }
        catch {
            print("DEBUG: Failed to save video data")
        }
    }
}

然后将文件上传到我们的API。虽然文件是 MP4,但无法在 Android 上播放。经检查,当我们比较编解码器数据时,该文件看起来与实际将在 Android 设备上播放的文件非常相似:

有人知道我该如何解决这个问题吗?

谢谢!

//MARK:- Convert iPhoneVideo(.mov) to mp4
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, preferredTimescale: 0)
        let range = CMTimeRangeMake(start: start, duration: 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)
    }
}
}


//MARK:- ImagePicker delegate methods
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
    {

if let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL {

                let avAsset = AVURLAsset(url: url, options: nil)
            avAsset.exportVideo(presetName: AVAssetExportPresetHighestQuality, outputFileType: AVFileType.mp4, fileExtension: "mp4") { (mp4Url) in
                print("Mp4 converted url : \(String(describing: mp4Url))")
                self.videoPath = mp4Url//videoURL//

            }

}

}
var exportSession: AVAssetExportSession!


func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            picker.dismiss(animated: true, completion: nil)

        guard let videoURL = (info[UIImagePickerController.InfoKey.mediaURL] as? URL) else { return }
        encodeVideo(videoURL)
    }

func encodeVideo(_ videoURL: URL)  {
        let avAsset = AVURLAsset(url: videoURL, options: nil)

        //Create Export session
        exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)

        let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
        let filePath = documentsDirectory.appendingPathComponent("rendered-Video.mp4")
        deleteFile(filePath)

        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
            DispatchQueue.main.async {
                Utility.stopActivityIndicator()

                switch self.exportSession!.status {
                case .failed:
                    self.view.makeToast(self.exportSession?.error?.localizedDescription ?? "")
                case .cancelled:
                    self.view.makeToast("Export canceled")
                case .completed:
                    if let url = self.exportSession.outputURL {
                        //Rendered Video URL
                    }
                default:
                    break
                }
            }
        })
    }

别忘了导入 AVFoundation

希望对您有所帮助!