Swift AVAssetTrack 未正确合并



尝试使用 AVMutableComposition 合并多个视频时,轨道在 avassettrack 中正确收集。但是,它们只是重叠,仅显示第二个视频。此外,输出的长度是第二个视频长度。

下面是合并函数:

public func mergeCapturedVideos(completion: @escaping (_ completedMovieURL: URL) -> Void) {
let mixComposition = AVMutableComposition()
let movieAssets: [AVAsset] = self.capturedMovieURLs.map({ AVAsset(url: $0) })
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>"
)>

如您所见,曲目正在正确添加,因此一定是插入时间变量的问题。但这也打印了预期的输出:

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 var 在每个循环中正确添加了每个轨道的持续时间。

这让我非常困惑,为什么每个视频的输出不是一部单一的电影?

你确定这是第二部只显示而不是第一部电影吗?

我相信正在发生的事情是这样的:

  1. 创建有效的输出网址let completeMovieURL: URL = self.capturedMovieURLs[0]
  2. 然后,您尝试执行导出,但导出失败,因此没有新视频写入输出 url
  3. 您检查if let url = exporter.outputURL这是否始终有效,因为您为其提供了有效视频的网址
  4. 要检查是否存在错误,您应该执行AVAssetExportSession status并且仅在completed时才应调用完成处理程序
  5. 因为有一个错误,没有写新电影,你的完成处理程序被调用了视频自拍.capturedMovieURLs[0],这就是你看到的

这解释了为什么您会看到 1 个视频的问题,但为什么合并失败,我认为这是由于 2 个原因,而不是因为您的时间范围:

  1. 您正在为每个视频创建一个新AVMutableCompositionTrack,而实际上您应该重复使用它并不断向其添加新视频。在循环外初始化
  2. 使用的AVAssetExportPresetAVAssetExportPresetHEVC1920x1080WithAlpha- 我建议不要使用 除非您要配置一些指令来支持此功能 格式,最好与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: $0) })
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)")
}
}
}
}
}
}

最新更新