【问题标题】:AVFoundation Crash on Exporting Video With Text Layer使用文本层导出视频时 AVFoundation 崩溃
【发布时间】:2020-07-15 15:53:28
【问题描述】:

我在业余时间正在开发一款适用于 iOS 的视频编辑应用。

在参加其他项目几周后,我刚刚恢复了工作,并且 - 即使我没有对代码进行任何重大更改 - 现在每次尝试导出我的视频作品时它都会崩溃

我检查并构建了与我当时成功上传到 TestFlight 完全相同的提交(并且它在多个设备上工作而没有崩溃),所以这可能是我更新后的最新 Xcode / iOS SDK 的问题那么呢?

代码在 _xpc_api_misuse 上崩溃,在一个线程上:

com.apple.coremedia.basicvideocompositor.output

调试导航器:

在崩溃时,调试导航器上有 70 多个线程,所以可能有问题,应用程序使用了太多线程(从未见过这么多)。


我的应用使用文本层在导出的视频上叠加了“水印”。玩了一圈,发现注释掉水印代码可以避免崩溃:

    guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
        return failure(ProjectError.failedToCreateExportSession)
    }
    guard let documents = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {
        return failure(ProjectError.temporaryOutputDirectoryNotFound)
    }
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd_HHmmss"
    let fileName = dateFormatter.string(from: Date())
    let fileExtension = "mov"
    let fileURL = documents.appendingPathComponent(fileName).appendingPathExtension(fileExtension)
    exporter.outputURL = fileURL

    exporter.outputFileType = AVFileType.mov
    exporter.shouldOptimizeForNetworkUse = true // check if needed

    // OFFENDING BLOCK (commenting out averts crash)
    if addWaterMark {
        let frame = CGRect(origin: .zero, size: videoComposition.renderSize)
        let watermark = WatermarkLayer(frame: frame)
        let parentLayer = CALayer()
        let videoLayer = CALayer()

        parentLayer.frame = frame
        videoLayer.frame = frame
        parentLayer.addSublayer(videoLayer)
        parentLayer.addSublayer(watermark)
        videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer)
    }
    // END OF OFFENDING BLOCK

    exporter.videoComposition = videoComposition

    exporter.exportAsynchronously {
    // etc.

水印层的代码是:

class WatermarkLayer: CATextLayer {

    private let defaultFontSize: CGFloat = 48

    private let rightMargin: CGFloat = 10
    private let bottomMargin: CGFloat = 10

    init(frame: CGRect) {
        super.init()
        guard let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String else {
            fatalError("!!!")
        }
        self.foregroundColor = CGColor.srgb(r: 255, g: 255, b: 255, a: 0.5)
        self.backgroundColor = CGColor.clear
        self.string = String(format: String.watermarkFormat, appName)
        self.font = CTFontCreateWithName(String.watermarkFontName as CFString, defaultFontSize, nil)
        self.fontSize = defaultFontSize
        self.shadowOpacity = 0.75
        self.alignmentMode = .right
        self.frame = frame
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented. Use init(frame:) instead.")
    }

    override func draw(in ctx: CGContext) {
        let height = self.bounds.size.height
        let fontSize = self.fontSize
        let yDiff = (height-fontSize) - fontSize/10 - bottomMargin // Bottom (minus margin)

        ctx.saveGState()
        ctx.translateBy(x: -rightMargin, y: yDiff)
        super.draw(in: ctx)
        ctx.restoreGState()
    }
}

有什么想法会发生什么吗?

也许我的代码做错了什么,由于某些 Apple 错误已得到修复或实现“漏洞”被堵塞,以某种方式在以前的 SDK 中“通过”?


更新:我下载了 Ray Wenderlich's sample project 用于视频编辑并尝试为视频添加“字幕”(我不得不调整太旧的项目,以便它可以在 Xcode 11 下编译)。

瞧,它以完全相同的方式崩溃


更新 2:我现在在设备(运行最新 iOS 13.5 的 iPhone 8)上进行了尝试,可以正常工作,没有崩溃。然而,iOS 13.5 的模拟器确实会崩溃。当我最初发布这个问题(iOS 13.4?)时,我确定它既是在设备上崩溃,也是在模拟器上崩溃。

我正在下载 iOS 12.0 模拟器进行检查,但距离它还有几 GB 的距离...

【问题讨论】:

  • 我还在 Apple 的论坛 (forums.developer.apple.com/message/422504#422504) 上发布了一个问题,以便它可以收集一些风滚草。
  • 您的问题解决了吗?我遇到了同样的问题。
  • @gstream79 最近没碰代码,忙于我的日常工作。有关最新进展,请参阅上面的“更新 2”。
  • 在两台设备上崩溃,iOS 13.4、13.5 的模拟器

标签: ios avfoundation avcomposition


【解决方案1】:

我也有同样的问题。在 iOS 13.4 之后开始,仅在模拟器上显示(设备工作正常)。如果我注释掉parentLayer.addSublayer(videoLayer),那么应用程序不会崩溃,但导出的视频不是所需的输出。

【讨论】:

  • 我的应用在模拟器和设备上崩溃。
  • 如果我注释掉完全相同的行(删除与文本层协调工作的视频子层),我也可以避免崩溃,但输出视频会导致黑屏。
  • 您的设备是否也在运行 13.4(或更高版本)并避免此问题?
  • 在 Xcode 12 和 iOS 14.0 模拟器上仍然发生 :(
  • 这有什么更新吗?仍然崩溃 Xcode 12.3,iOS 14.3 :((
【解决方案2】:

遇到同样的问题,但仅限于模拟器 (Xcode 12.4 (12D4e))。

经过一番研究,我发现这次崩溃是由AVVideoCompositionCoreAnimationTool's 导致的

+videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:inLayer:

我通过用下面的替换它来修复它(但是我们需要以这种方式处理instruction.layerInstructions):

+videoCompositionCoreAnimationToolWithAdditionalLayer:asTrackID:

下面是一个在真实设备和模拟器上都可以使用的示例代码(因为 OP 没有明确标记 Swift,我将在这里复制我的 Objective-C 示例):

...

// Prepare watermark layer
CALayer *watermarkLayer = ...;
CMPersistentTrackID watermarkLayerTrackID = [asset unusedTrackID];
// !!! NOTE#01: Use as additional layer here instead of animation layer.
videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithAdditionalLayer:watermarkLayer asTrackID:watermarkLayerTrackID];
  
// Create video composition instruction
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);

// - Watermark layer instruction
// !!! NOTE#02: Make this instruction track watermark layer by the `trackID`.
AVMutableVideoCompositionLayerInstruction *watermarkLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstruction];
watermarkLayerInstruction.trackID = watermarkLayerTrackID;

// - Video track layer instruction
AVAssetTrack *videoTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
AVMutableVideoCompositionLayerInstruction *videoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];

// Watermark layer above video layer here.
instruction.layerInstructions = @[
  watermarkLayerInstruction,
  videoLayerInstruction,
];
  
videoComposition.instructions = @[instruction];

// Export the video w/ watermark.
AVAssetExportSession *exportSession = ...;
...
exportSession.videoComposition = videoComposition;

...
  

顺便说一句,如果您只需要添加图像作为水印,使用AVVideoComposition's 的另一种解决方案

-videoCompositionWithAsset:applyingCIFiltersWithHandler:

在真实设备和模拟器上也能很好地工作,但我对其进行了测试,发现它的速度较慢。似乎这种方式更适合视频混合器/过滤器。

【讨论】:

【解决方案3】:

这在 iOS 14.5 中为我修复了它:

public static var isSimulator: Bool {
  #if targetEnvironment(simulator)
  true
  #else
  false
  #endif
}

// ...

let export = AVAssetExportSession(
  asset: composition,
  presetName: isSimulator ? AVAssetExportPresetPassthrough : AVAssetExportPresetHighestQuality
)

edit:虽然实际上并不像在真实设备上那样渲染。编辑会被忽略...

【讨论】:

  • 谢谢,@yspreen -- 这并不能解决问题,它只是在模拟器上运行时绕过它,这是一个好的开始,因为崩溃似乎只发生在模拟器上。有了这个,至少它不会崩溃和中断开发。
  • 是的,我在发帖后注意到了这一点。现在改变我的答案
猜你喜欢
  • 1970-01-01
  • 2018-04-29
  • 2011-06-12
  • 2015-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多