You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双视频图层显示异常:两层均展示同一视频问题排查

视频合成两层均显示同一视频的问题排查与解决

问题现象

最终输出视频中,视频图层与叠加图层均仅显示来自videoURL的视频,未展示overlayVideoURL的视频。

已尝试操作

  • 检查并确认CALayer的frame设置正确
  • 验证AVVideoCompositionCoreAnimationTool的使用无误
  • 确认AVMutableComposition已插入两个视频轨道

疑问

  1. 视频图层的配置是否存在错误?
  2. 如何确保每个图层正确展示对应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:是对单个视频轨道做后期动画处理的接口,并非用于多轨道到多图层的映射,导致两个视频轨道的内容都渲染到了第一个图层上。

具体修改步骤

  1. 替换动画工具初始化方式
    改用videoLayers:usingRenderingMode:方法,将每个视频轨道与对应的CALayer绑定,数组顺序与轨道添加顺序一致(第一个轨道对应第一个图层,第二个轨道对应第二个图层):

    videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(
        videoLayers: [videoLayer, overlayVideoLayer],
        usingRenderingMode: .passthrough
    )
    
  2. 调整叠加层的图层指令
    需要给叠加层轨道添加缩放、平移变换,让视频内容适配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)
    
  3. 统一时间范围
    避免叠加视频时长不足出现空白,取两个视频的最小时长作为合成时间范围:

    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,提问作者Игорь Шварев

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 19:14:53