AVAssetExportSession 不适用于设备,但适用于模拟器(AVFoundationErrorDomain 代码 = -11800,未知错误代码 -12780)
AVAssetExportSession not working on devices, but working on simulator (AVFoundationErrorDomain Code = -11800, Unknown Error code -12780)
编辑: 为了让有兴趣检查这个问题的人更容易,我添加了一个演示项目到 this github repository.
问题
我见过几个有相同错误的问题,但是 none 那里找到的解决方案对我有帮助。我想试试我的运气。
我正在尝试按原样导出视频,主要是为了了解 AVFoundation 和 AVAssetExportSession。我的导出在模拟器上工作得很好,但在我试过的任何 iOS 设备上都不起作用(即 iPhone X 和 iPhone XR 运行 iOS 每个 12 个)。我主要按照 link 上的 Ray Wenderleich 教程执行视频导出:https://www.raywenderlich.com/2734-avfoundation-tutorial-adding-overlays-and-animations-to-videos
非常感谢有关该主题的任何帮助。我的代码如下:
正在检索我添加到 App Bundle 中名为 Demo.mp4 的视频的 URL:
@objc func export() {
let urlString = Bundle.main.path(forResource: "Demo", ofType: ".mp4")!
let url = URL(fileURLWithPath: urlString)
ExportManager.shared.exportWithAVFoundation(url:url) { (outputUrl, errorString) in
if let outputUrl = outputUrl {
self.playVideo(url: outputUrl)
} else if let errorString = errorString {
print("ERROR: \(errorString)")
}
}
}
我在ExportManager中的导出功能如下(抱歉很长)
func exportWithAVFoundation(url: URL, completion: @escaping (_ outputUrl: URL?, _ errorString: String?) -> ()) {
let asset = AVAsset(url: url)
print("URL IS \(url)")
guard let avAssetTrack = asset.tracks(withMediaType: .video).first else {
completion(nil, "Couldn't Create Asset Track")
return
}
let mutableComposition = AVMutableComposition()
guard let videoTrack = mutableComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else { return }
try? videoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: avAssetTrack, at: .zero)
videoTrack.preferredTransform = avAssetTrack.preferredTransform
if let audioAssetTrack = asset.tracks(withMediaType: .audio).first {
let audioTrack = mutableComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
try? audioTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: audioAssetTrack, at: .zero)
}
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration)
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
// Fix video orientation
var videoAssetOrientation = UIImage.Orientation.up
var isVideoAssetPortrait = false
let videoTransform = avAssetTrack.preferredTransform
switch (videoTransform.a, videoTransform.b, videoTransform.c, videoTransform.c) {
case (0, 1.0, -1.0, 0):
videoAssetOrientation = .right
isVideoAssetPortrait = true
case(0, -1.0, 1.0, 0):
videoAssetOrientation = .left
isVideoAssetPortrait = true
case(1.0, 0, 0, 1.0):
videoAssetOrientation = .up
case(-1.0, 0, 0, -1.0):
videoAssetOrientation = .down
default:
break
}
var naturalSize = avAssetTrack.naturalSize
switch (videoAssetOrientation, isVideoAssetPortrait) {
case (.right, true):
naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
case (.left, true):
naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
case (.leftMirrored, true):
naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
case (.rightMirrored, true):
naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
default:
break
}
videoLayerInstruction.setTransform(avAssetTrack.preferredTransform, at: .zero)
videoLayerInstruction.setOpacity(0, at: asset.duration)
mainInstruction.layerInstructions = [videoLayerInstruction]
let mainCompositionInstruction = AVMutableVideoComposition()
mainCompositionInstruction.renderSize = naturalSize
mainCompositionInstruction.instructions = [mainInstruction]
mainCompositionInstruction.frameDuration = CMTimeMake(value: 1, timescale: 30);
let documentsDirectoryURL = createPath()
guard let exporter = AVAssetExportSession(asset: mutableComposition, presetName: AVAssetExportPresetHighestQuality) else {
print("Couldnt create AVAssetExportSession")
completion(nil, "Couldn't Create AVAssetExportSession")
return
}
exporter.outputURL = documentsDirectoryURL
exporter.outputFileType = .mov
exporter.shouldOptimizeForNetworkUse = true
exporter.videoComposition = mainCompositionInstruction
exporter.exportAsynchronously {
if let error = exporter.error {
print(error)
completion(nil, error.localizedDescription)
return
}
completion(exporter.outputURL, nil)
print("Finished Exporting")
}
}
我尝试做的一些事情是将 AudioTrack 添加到作品中(我之前没有包括在内)。没有帮助它在实际设备上工作,但至少我导出的视频现在有音频了。
我还尝试将预设质量更改为直通而不是最高质量,因为我从其他帖子中了解到这可能有帮助,但无济于事。
编辑:
添加了 createPath 函数:
func createPath() -> URL {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentDirectory = paths.first!
let myPathDocs = documentDirectory.appending("FinalVideo.mov")
let url = URL(fileURLWithPath: myPathDocs)
if FileManager.default.fileExists(atPath: myPathDocs) {
try? FileManager.default.removeItem(atPath: myPathDocs)
}
return url
}
注意:
createPath() 只是在目录中创建一个有效路径来保存导出的视频。如果在导出之前该路径中存在文件,它将被删除。
问题是您将字符串附加到另一个字符串,导致文件路径错误,例如 file:///var/mobile/Containers/Data/Application//DocumentsFinalVideo.mov
您应该改用 appendingPathComponent():
func createPath() -> URL {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentDirectory = URL(fileURLWithPath: paths.first!)
let url = documentDirectory.appendingPathComponent("FinalVideo.mov")
if FileManager.default.fileExists(atPath: url.path) {
try? FileManager.default.removeItem(at: url)
}
return url
}
您必须使用默认方法:
appendingPathComponent()
编辑: 为了让有兴趣检查这个问题的人更容易,我添加了一个演示项目到 this github repository.
问题
我见过几个有相同错误的问题,但是 none 那里找到的解决方案对我有帮助。我想试试我的运气。
我正在尝试按原样导出视频,主要是为了了解 AVFoundation 和 AVAssetExportSession。我的导出在模拟器上工作得很好,但在我试过的任何 iOS 设备上都不起作用(即 iPhone X 和 iPhone XR 运行 iOS 每个 12 个)。我主要按照 link 上的 Ray Wenderleich 教程执行视频导出:https://www.raywenderlich.com/2734-avfoundation-tutorial-adding-overlays-and-animations-to-videos
非常感谢有关该主题的任何帮助。我的代码如下:
正在检索我添加到 App Bundle 中名为 Demo.mp4 的视频的 URL:
@objc func export() {
let urlString = Bundle.main.path(forResource: "Demo", ofType: ".mp4")!
let url = URL(fileURLWithPath: urlString)
ExportManager.shared.exportWithAVFoundation(url:url) { (outputUrl, errorString) in
if let outputUrl = outputUrl {
self.playVideo(url: outputUrl)
} else if let errorString = errorString {
print("ERROR: \(errorString)")
}
}
}
我在ExportManager中的导出功能如下(抱歉很长)
func exportWithAVFoundation(url: URL, completion: @escaping (_ outputUrl: URL?, _ errorString: String?) -> ()) {
let asset = AVAsset(url: url)
print("URL IS \(url)")
guard let avAssetTrack = asset.tracks(withMediaType: .video).first else {
completion(nil, "Couldn't Create Asset Track")
return
}
let mutableComposition = AVMutableComposition()
guard let videoTrack = mutableComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else { return }
try? videoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: avAssetTrack, at: .zero)
videoTrack.preferredTransform = avAssetTrack.preferredTransform
if let audioAssetTrack = asset.tracks(withMediaType: .audio).first {
let audioTrack = mutableComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
try? audioTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: audioAssetTrack, at: .zero)
}
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration)
let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
// Fix video orientation
var videoAssetOrientation = UIImage.Orientation.up
var isVideoAssetPortrait = false
let videoTransform = avAssetTrack.preferredTransform
switch (videoTransform.a, videoTransform.b, videoTransform.c, videoTransform.c) {
case (0, 1.0, -1.0, 0):
videoAssetOrientation = .right
isVideoAssetPortrait = true
case(0, -1.0, 1.0, 0):
videoAssetOrientation = .left
isVideoAssetPortrait = true
case(1.0, 0, 0, 1.0):
videoAssetOrientation = .up
case(-1.0, 0, 0, -1.0):
videoAssetOrientation = .down
default:
break
}
var naturalSize = avAssetTrack.naturalSize
switch (videoAssetOrientation, isVideoAssetPortrait) {
case (.right, true):
naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
case (.left, true):
naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
case (.leftMirrored, true):
naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
case (.rightMirrored, true):
naturalSize = CGSize(width: avAssetTrack.naturalSize.height, height: avAssetTrack.naturalSize.width)
default:
break
}
videoLayerInstruction.setTransform(avAssetTrack.preferredTransform, at: .zero)
videoLayerInstruction.setOpacity(0, at: asset.duration)
mainInstruction.layerInstructions = [videoLayerInstruction]
let mainCompositionInstruction = AVMutableVideoComposition()
mainCompositionInstruction.renderSize = naturalSize
mainCompositionInstruction.instructions = [mainInstruction]
mainCompositionInstruction.frameDuration = CMTimeMake(value: 1, timescale: 30);
let documentsDirectoryURL = createPath()
guard let exporter = AVAssetExportSession(asset: mutableComposition, presetName: AVAssetExportPresetHighestQuality) else {
print("Couldnt create AVAssetExportSession")
completion(nil, "Couldn't Create AVAssetExportSession")
return
}
exporter.outputURL = documentsDirectoryURL
exporter.outputFileType = .mov
exporter.shouldOptimizeForNetworkUse = true
exporter.videoComposition = mainCompositionInstruction
exporter.exportAsynchronously {
if let error = exporter.error {
print(error)
completion(nil, error.localizedDescription)
return
}
completion(exporter.outputURL, nil)
print("Finished Exporting")
}
}
我尝试做的一些事情是将 AudioTrack 添加到作品中(我之前没有包括在内)。没有帮助它在实际设备上工作,但至少我导出的视频现在有音频了。
我还尝试将预设质量更改为直通而不是最高质量,因为我从其他帖子中了解到这可能有帮助,但无济于事。
编辑:
添加了 createPath 函数:
func createPath() -> URL {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentDirectory = paths.first!
let myPathDocs = documentDirectory.appending("FinalVideo.mov")
let url = URL(fileURLWithPath: myPathDocs)
if FileManager.default.fileExists(atPath: myPathDocs) {
try? FileManager.default.removeItem(atPath: myPathDocs)
}
return url
}
注意: createPath() 只是在目录中创建一个有效路径来保存导出的视频。如果在导出之前该路径中存在文件,它将被删除。
问题是您将字符串附加到另一个字符串,导致文件路径错误,例如 file:///var/mobile/Containers/Data/Application/
您应该改用 appendingPathComponent():
func createPath() -> URL {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentDirectory = URL(fileURLWithPath: paths.first!)
let url = documentDirectory.appendingPathComponent("FinalVideo.mov")
if FileManager.default.fileExists(atPath: url.path) {
try? FileManager.default.removeItem(at: url)
}
return url
}
您必须使用默认方法: appendingPathComponent()