如何从 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
希望对您有所帮助!
我正在使用 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
希望对您有所帮助!