Swift AVAssetTrack 没有正确合并
Swift AVAssetTrack not merging properly
尝试使用 AVMutableComposition 合并多个视频,轨道在 avassettrack 中正确收集。但是,它们只是重叠并且仅显示第二个视频。输出的长度也是第二个视频的长度。
这里是合并函数:
public func mergeCapturedVideos(completion: @escaping (_ completedMovieURL: URL) -> Void) {
let mixComposition = AVMutableComposition()
let movieAssets: [AVAsset] = self.capturedMovieURLs.map({ AVAsset(url: [=11=]) })
var insertTime: CMTime = CMTime.zero
if movieAssets.count == self.capturedMovieURLs.count {
for movieAsset in movieAssets {
do {
if let compositionVideoTrack: AVMutableCompositionTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) {
let tracks: [AVAssetTrack] = movieAsset.tracks(withMediaType: .video)
let assetTrack: AVAssetTrack = tracks[0] as AVAssetTrack
try compositionVideoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: movieAsset.duration), of: assetTrack, at: insertTime)
print("1 \(insertTime)")
insertTime = CMTimeAdd(insertTime, movieAsset.duration)
print("2 \(insertTime)")
}
} catch let error as NSError {
print("Error merging movies: \(error)")
}
print("MIX: \(mixComposition)")
}
let completeMovieURL: URL = self.capturedMovieURLs[0]
if let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHEVC1920x1080WithAlpha) {
exporter.outputURL = completeMovieURL
exporter.outputFileType = .mp4
exporter.exportAsynchronously {
if let url = exporter.outputURL {
completion(url)
} else if let error = exporter.error {
print("Merge exporter error: \(error)")
}
}
}
}
}
这是“MIX:”打印语句输出的内容:
MIX: <AVMutableComposition: 0x2815670a0 tracks = (
"<AVMutableCompositionTrack: 0x281567ea0 trackID = 1, mediaType = vide, editCount = 1>",
"<AVMutableCompositionTrack: 0x281567da0 trackID = 2, mediaType = vide, editCount = 2>"
)>
如您所见,曲目已正确添加,因此一定是 insertTime 变量的问题。但这也打印了预期的输出:
1 CMTime(value: 0, timescale: 1, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
2 CMTime(value: 2121, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
1 CMTime(value: 2121, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
2 CMTime(value: 3560, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
如图所示,insertTime 变量正确地添加了每个循环中每个轨道的持续时间。
这让我很困惑,为什么每个视频输出的不是单一的电影?
你确定这是第二部电影而不是第一部电影吗?
我相信发生的事情是这样的:
- 您创建了一个有效的输出 url
let completeMovieURL: URL = self.capturedMovieURLs[0]
- 然后您尝试导出,但导出失败,因此没有新视频写入输出 url
- 您检查
if let url = exporter.outputURL
这是否始终有效,因为您向它提供了有效视频的 url
- 要检查是否有错误,你应该做一个
AVAssetExportSession status
并且只在 completed
上你应该调用你的完成处理程序
- 因为出现错误,没有编写新电影,您的完成处理程序随视频一起调用 self.capturedMovieURLs[0],这就是您所看到的
这解释了为什么你看到 1 个视频,但为什么合并失败,我认为这是由于 2 个原因,而不是因为你的时间范围:
- 您正在为每个视频创建一个新的
AVMutableCompositionTrack
,而实际上您应该重复使用它并不断向其中添加新视频。在循环外初始化这个
- 使用的
AVAssetExportPreset
是
AVAssetExportPresetHEVC1920x1080WithAlpha
- 我建议不要使用
这除非你想配置一些指令来支持这个
格式,最好使用 AVAssetExportPresetHighestQuality
如果你
想让事情简单化
这是我根据上面的评论对您现有的代码所做的一些更新,希望您能得到您想要的输出
public func mergeCapturedVideos(completion: @escaping (_ completedMovieURL: URL) -> Void)
{
// No change to your code
let mixComposition = AVMutableComposition()
let movieAssets: [AVAsset] = self.capturedMovieURLs.map({ AVAsset(url: [=10=]) })
var insertTime = CMTime.zero
// Initialize a AVMutableCompositionTrack outside the loop
guard let compositionVideoTrack
= mixComposition.addMutableTrack(withMediaType: .video,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
else
{
// error initializing video track
return
}
if movieAssets.count == self.capturedMovieURLs.count
{
// Use the existing compositionVideoTrack in the loop, don't create a new
// one each time
for movieAsset in movieAssets
{
do {
let tracks: [AVAssetTrack] = movieAsset.tracks(withMediaType: .video)
let assetTrack: AVAssetTrack = tracks[0] as AVAssetTrack
// Insert a new track into the existing
try compositionVideoTrack.insertTimeRange(CMTimeRange(start: .zero,
duration: movieAsset.duration),
of: assetTrack,
at: insertTime)
print("1 \(insertTime)")
insertTime = CMTimeAdd(insertTime, movieAsset.duration)
print("2 \(insertTime)")
}
catch let error as NSError {
print("Error merging movies: \(error)")
}
print("MIX: \(mixComposition)")
}
// Configure where the exported file will be stored
let documentsURL = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask)[0]
// Generate a file name or reuse your existing url
// Not sure if AVAssetExportSession can overwrite
let fileName = "\(UUID().uuidString).mp4"
let dirPath = documentsURL.appendingPathComponent(fileName)
let outputFileURL = dirPath
// Use the preset `AVAssetExportPresetHighestQuality` if you don't want
// to mess with additional configuration
if let exporter = AVAssetExportSession(asset: mixComposition,
presetName: AVAssetExportPresetHighestQuality)
{
exporter.outputURL = outputFileURL
exporter.outputFileType = .mp4
// Check if export has succeeded
exporter.exportAsynchronously
{
switch exporter.status
{
case .completed:
if let url = exporter.outputURL
{
DispatchQueue.main.async
{
completion(url)
}
}
default:
if let error = exporter.error
{
print("Merge exporter error: \(error)")
}
}
}
}
}
}
尝试使用 AVMutableComposition 合并多个视频,轨道在 avassettrack 中正确收集。但是,它们只是重叠并且仅显示第二个视频。输出的长度也是第二个视频的长度。
这里是合并函数:
public func mergeCapturedVideos(completion: @escaping (_ completedMovieURL: URL) -> Void) {
let mixComposition = AVMutableComposition()
let movieAssets: [AVAsset] = self.capturedMovieURLs.map({ AVAsset(url: [=11=]) })
var insertTime: CMTime = CMTime.zero
if movieAssets.count == self.capturedMovieURLs.count {
for movieAsset in movieAssets {
do {
if let compositionVideoTrack: AVMutableCompositionTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) {
let tracks: [AVAssetTrack] = movieAsset.tracks(withMediaType: .video)
let assetTrack: AVAssetTrack = tracks[0] as AVAssetTrack
try compositionVideoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: movieAsset.duration), of: assetTrack, at: insertTime)
print("1 \(insertTime)")
insertTime = CMTimeAdd(insertTime, movieAsset.duration)
print("2 \(insertTime)")
}
} catch let error as NSError {
print("Error merging movies: \(error)")
}
print("MIX: \(mixComposition)")
}
let completeMovieURL: URL = self.capturedMovieURLs[0]
if let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHEVC1920x1080WithAlpha) {
exporter.outputURL = completeMovieURL
exporter.outputFileType = .mp4
exporter.exportAsynchronously {
if let url = exporter.outputURL {
completion(url)
} else if let error = exporter.error {
print("Merge exporter error: \(error)")
}
}
}
}
}
这是“MIX:”打印语句输出的内容:
MIX: <AVMutableComposition: 0x2815670a0 tracks = (
"<AVMutableCompositionTrack: 0x281567ea0 trackID = 1, mediaType = vide, editCount = 1>",
"<AVMutableCompositionTrack: 0x281567da0 trackID = 2, mediaType = vide, editCount = 2>"
)>
如您所见,曲目已正确添加,因此一定是 insertTime 变量的问题。但这也打印了预期的输出:
1 CMTime(value: 0, timescale: 1, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
2 CMTime(value: 2121, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
1 CMTime(value: 2121, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
2 CMTime(value: 3560, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
如图所示,insertTime 变量正确地添加了每个循环中每个轨道的持续时间。
这让我很困惑,为什么每个视频输出的不是单一的电影?
你确定这是第二部电影而不是第一部电影吗?
我相信发生的事情是这样的:
- 您创建了一个有效的输出 url
let completeMovieURL: URL = self.capturedMovieURLs[0]
- 然后您尝试导出,但导出失败,因此没有新视频写入输出 url
- 您检查
if let url = exporter.outputURL
这是否始终有效,因为您向它提供了有效视频的 url - 要检查是否有错误,你应该做一个
AVAssetExportSession status
并且只在completed
上你应该调用你的完成处理程序 - 因为出现错误,没有编写新电影,您的完成处理程序随视频一起调用 self.capturedMovieURLs[0],这就是您所看到的
这解释了为什么你看到 1 个视频,但为什么合并失败,我认为这是由于 2 个原因,而不是因为你的时间范围:
- 您正在为每个视频创建一个新的
AVMutableCompositionTrack
,而实际上您应该重复使用它并不断向其中添加新视频。在循环外初始化这个 - 使用的
AVAssetExportPreset
是AVAssetExportPresetHEVC1920x1080WithAlpha
- 我建议不要使用 这除非你想配置一些指令来支持这个 格式,最好使用AVAssetExportPresetHighestQuality
如果你 想让事情简单化
这是我根据上面的评论对您现有的代码所做的一些更新,希望您能得到您想要的输出
public func mergeCapturedVideos(completion: @escaping (_ completedMovieURL: URL) -> Void)
{
// No change to your code
let mixComposition = AVMutableComposition()
let movieAssets: [AVAsset] = self.capturedMovieURLs.map({ AVAsset(url: [=10=]) })
var insertTime = CMTime.zero
// Initialize a AVMutableCompositionTrack outside the loop
guard let compositionVideoTrack
= mixComposition.addMutableTrack(withMediaType: .video,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
else
{
// error initializing video track
return
}
if movieAssets.count == self.capturedMovieURLs.count
{
// Use the existing compositionVideoTrack in the loop, don't create a new
// one each time
for movieAsset in movieAssets
{
do {
let tracks: [AVAssetTrack] = movieAsset.tracks(withMediaType: .video)
let assetTrack: AVAssetTrack = tracks[0] as AVAssetTrack
// Insert a new track into the existing
try compositionVideoTrack.insertTimeRange(CMTimeRange(start: .zero,
duration: movieAsset.duration),
of: assetTrack,
at: insertTime)
print("1 \(insertTime)")
insertTime = CMTimeAdd(insertTime, movieAsset.duration)
print("2 \(insertTime)")
}
catch let error as NSError {
print("Error merging movies: \(error)")
}
print("MIX: \(mixComposition)")
}
// Configure where the exported file will be stored
let documentsURL = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask)[0]
// Generate a file name or reuse your existing url
// Not sure if AVAssetExportSession can overwrite
let fileName = "\(UUID().uuidString).mp4"
let dirPath = documentsURL.appendingPathComponent(fileName)
let outputFileURL = dirPath
// Use the preset `AVAssetExportPresetHighestQuality` if you don't want
// to mess with additional configuration
if let exporter = AVAssetExportSession(asset: mixComposition,
presetName: AVAssetExportPresetHighestQuality)
{
exporter.outputURL = outputFileURL
exporter.outputFileType = .mp4
// Check if export has succeeded
exporter.exportAsynchronously
{
switch exporter.status
{
case .completed:
if let url = exporter.outputURL
{
DispatchQueue.main.async
{
completion(url)
}
}
default:
if let error = exporter.error
{
print("Merge exporter error: \(error)")
}
}
}
}
}
}