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

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

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

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

  1. 您创建了一个有效的输出 url let completeMovieURL: URL = self.capturedMovieURLs[0]
  2. 然后您尝试导出,但导出失败,因此没有新视频写入输出 url
  3. 您检查 if let url = exporter.outputURL 这是否始终有效,因为您向它提供了有效视频的 url
  4. 要检查是否有错误,你应该做一个 AVAssetExportSession status 并且只在 completed 上你应该调用你的完成处理程序
  5. 因为出现错误,没有编写新电影,您的完成处理程序随视频一起调用 self.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: [=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)")
                        }
                }
            }
        }
    }
}