双视频图层显示异常:两层均展示同一视频问题排查
视频合成两层均显示同一视频的问题排查与解决
问题现象
最终输出视频中,视频图层与叠加图层均仅显示来自videoURL的视频,未展示overlayVideoURL的视频。
已尝试操作
- 检查并确认CALayer的frame设置正确
- 验证AVVideoCompositionCoreAnimationTool的使用无误
- 确认AVMutableComposition已插入两个视频轨道
疑问
- 视频图层的配置是否存在错误?
- 如何确保每个图层正确展示对应URL的视频?
补充说明:视频已正确加载并插入至AVMutableComposition,但最终合成视频两层均显示videoURL的视频。
相关代码
func makeVideo(videoURL: URL, overlayVideoURL: URL, forName name: String, onComplete: @escaping (URL?) -> Void) { let asset = AVURLAsset(url: videoURL) let overlayAsset = AVURLAsset(url: overlayVideoURL) let composition = AVMutableComposition() guard let compositionTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid), let assetTrack = asset.tracks(withMediaType: .video).first, let overlayCompositionTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid), let overlayAssetTrack = overlayAsset.tracks(withMediaType: .video).first else { print("Something is wrong with the assets.") onComplete(nil) return } do { let timeRange = CMTimeRange(start: .zero, duration: asset.duration) try compositionTrack.insertTimeRange(timeRange, of: assetTrack, at: .zero) try overlayCompositionTrack.insertTimeRange(timeRange, of: overlayAssetTrack, at: .zero) } catch { print(error) onComplete(nil) return } let videoInfo = orientation(from: assetTrack.preferredTransform) let videoSize = videoInfo.isPortrait ? CGSize(width: assetTrack.naturalSize.height, height: assetTrack.naturalSize.width) : assetTrack.naturalSize let videoComposition = AVMutableVideoComposition() videoComposition.renderSize = videoSize videoComposition.frameDuration = CMTime(value: 1, timescale: 30) let instruction = AVMutableVideoCompositionInstruction() instruction.timeRange = CMTimeRange(start: .zero, duration: composition.duration) let layerInstruction = compositionLayerInstruction(for: compositionTrack, assetTrack: assetTrack) let overlayLayerInstruction = compositionLayerInstruction(for: overlayCompositionTrack, assetTrack: overlayAssetTrack) instruction.layerInstructions = [layerInstruction, overlayLayerInstruction] videoComposition.instructions = [instruction] let videoLayer = CALayer() videoLayer.frame = CGRect(origin: .zero, size: videoSize) let overlayVideoLayer = CALayer() overlayVideoLayer.frame = CGRect(x: videoSize.width / 4, y: videoSize.height / 4, width: videoSize.width / 2, height: videoSize.height / 2) let outputLayer = CALayer() outputLayer.frame = CGRect(origin: .zero, size: videoSize) outputLayer.addSublayer(videoLayer) outputLayer.addSublayer(overlayVideoLayer) videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayers: [videoLayer, overlayVideoLayer], in: outputLayer) guard let export = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else { print("Cannot create export session.") onComplete(nil) return } let videoName = UUID().uuidString let exportURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(videoName).appendingPathExtension("mov") export.videoComposition = videoComposition export.outputFileType = .mov export.outputURL = exportURL export.exportAsynchronously { DispatchQueue.main.async { switch export.status { case .completed: onComplete(exportURL) default: print("Something went wrong during export.") print(export.error ?? "unknown error") onComplete(nil) } } } }
问题原因与解决方法
核心问题在于错误使用了AVVideoCompositionCoreAnimationTool的初始化方法:你当前用的postProcessingAsVideoLayers:in:是对单个视频轨道做后期动画处理的接口,并非用于多轨道到多图层的映射,导致两个视频轨道的内容都渲染到了第一个图层上。
具体修改步骤
替换动画工具初始化方式
改用videoLayers:usingRenderingMode:方法,将每个视频轨道与对应的CALayer绑定,数组顺序与轨道添加顺序一致(第一个轨道对应第一个图层,第二个轨道对应第二个图层):videoComposition.animationTool = AVVideoCompositionCoreAnimationTool( videoLayers: [videoLayer, overlayVideoLayer], usingRenderingMode: .passthrough )调整叠加层的图层指令
需要给叠加层轨道添加缩放、平移变换,让视频内容适配overlayVideoLayer的尺寸和位置,修改compositionLayerInstruction方法:func compositionLayerInstruction(for compositionTrack: AVCompositionTrack, assetTrack: AVAssetTrack, overlayFrame: CGRect) -> AVMutableVideoCompositionLayerInstruction { let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack) let baseTransform = assetTrack.preferredTransform let naturalSize = assetTrack.naturalSize // 处理叠加层轨道 if compositionTrack == composition.tracks(withMediaType: .video)[1] { // 计算缩放比例适配叠加层尺寸 let scaleX = overlayFrame.width / naturalSize.width let scaleY = overlayFrame.height / naturalSize.height let scaleTransform = CGAffineTransform(scaleX: scaleX, y: scaleY) // 平移到叠加层位置 let translateTransform = CGAffineTransform(translationX: overlayFrame.origin.x, y: overlayFrame.origin.y) // 合并变换:先处理原视频方向,再缩放平移 let finalTransform = baseTransform.concatenating(scaleTransform).concatenating(translateTransform) instruction.setTransform(finalTransform, at: .zero) } else { // 处理主视频轨道的方向适配 let videoInfo = orientation(from: baseTransform) let renderSize = videoInfo.isPortrait ? CGSize(width: naturalSize.height, height: naturalSize.width) : naturalSize let adjustTransform = baseTransform.concatenating(CGAffineTransform(translationX: renderSize.width/2, y: renderSize.height/2)) .concatenating(CGAffineTransform(rotationAngle: CGFloat(videoInfo.rotationAngle))) .concatenating(CGAffineTransform(translationX: -renderSize.width/2, y: -renderSize.height/2)) instruction.setTransform(adjustTransform, at: .zero) } return instruction }调用时传入叠加层的frame:
let overlayLayerInstruction = compositionLayerInstruction(for: overlayCompositionTrack, assetTrack: overlayAssetTrack, overlayFrame: overlayVideoLayer.frame)统一时间范围
避免叠加视频时长不足出现空白,取两个视频的最小时长作为合成时间范围:let minDuration = CMTimeMinimum(asset.duration, overlayAsset.duration) let timeRange = CMTimeRange(start: .zero, duration: minDuration) try compositionTrack.insertTimeRange(timeRange, of: assetTrack, at: .zero) try overlayCompositionTrack.insertTimeRange(timeRange, of: overlayAssetTrack, at: .zero)
内容的提问来源于stack exchange,提问作者Игорь Шварев
相关产品推荐
相关产品推荐

