I am putting together a simple video editor app for iOS. The videos are exported with a "watermark" in the form of a text overlay (e.g. "This video was made with XYZ").
The app (still a prototype) was working until around February this year. Then I got busy, moved to other projects and stopped working on it for a while.
About a months ago, I resumed work on the app but suddenly noticed that it was crashing whenever I attempted to export any video.
The crash looks like this:
libxpc.dylib`_xpc_api_misuse:
0x7fff51c53154 <+0>: pushq %rbp
0x7fff51c53155 <+1>: movq %rsp, %rbp
0x7fff51c53158 <+4>: pushq %rbx
0x7fff51c53159 <+5>: subq $0xa8, %rsp
0x7fff51c53160 <+12>: movq %rdi, %r9
0x7fff51c53163 <+15>: movaps 0xdba6(%rip), %xmm0 ; __xpcVersionNumber + 160
0x7fff51c5316a <+22>: leaq -0xb0(%rbp), %rbx
0x7fff51c53171 <+29>: movaps %xmm0, 0x90(%rbx)
0x7fff51c53178 <+36>: movaps %xmm0, 0x80(%rbx)
0x7fff51c5317f <+43>: movaps %xmm0, 0x70(%rbx)
0x7fff51c53183 <+47>: movaps %xmm0, 0x60(%rbx)
0x7fff51c53187 <+51>: movaps %xmm0, 0x50(%rbx)
0x7fff51c5318b <+55>: movaps %xmm0, 0x40(%rbx)
0x7fff51c5318f <+59>: movaps %xmm0, 0x30(%rbx)
0x7fff51c53193 <+63>: movaps %xmm0, 0x20(%rbx)
0x7fff51c53197 <+67>: movaps %xmm0, 0x10(%rbx)
0x7fff51c5319b <+71>: movaps %xmm0, (%rbx)
0x7fff51c5319e <+74>: leaq 0x1150d(%rip), %r8 ; "XPC API Misuse: %s"
0x7fff51c531a5 <+81>: movl $0xa0, %esi
0x7fff51c531aa <+86>: movl $0xa0, %ecx
0x7fff51c531af <+91>: movq %rbx, %rdi
0x7fff51c531b2 <+94>: movl $0x0, %edx
0x7fff51c531b7 <+99>: xorl %eax, %eax
0x7fff51c531b9 <+101>: callq 0x7fff51c5fe18 ; symbol stub for: __snprintf_chk
0x7fff51c531be <+106>: movq %rbx, 0x380787c3(%rip) ; gCRAnnotations + 8
0x7fff51c531c5 <+113>: leaq 0x114f9(%rip), %rax ; "API Misuse"
0x7fff51c531cc <+120>: movq %rax, 0x380787bd(%rip) ; gCRAnnotations + 16
-> 0x7fff51c531d3 <+127>: ud2 < Thread 55: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
After experimenting a bit by eliminating features, I found out that the app crashes only if the exported video compositions contain the text overlay: if I comment out the code responsible for overlaying the text layer, the issue resolves.
This is the code I am using to export the video composition:
func export(completion: @escaping (() -> Void), failure: @escaping ((Error) -> Void)) {
let exportQuality = AVAssetExportPresetHighestQuality
guard let exporter = AVAssetExportSession(asset: composition, presetName: exportQuality) 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
if shouldAddWatermark {
// Watermark overlay:
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)
}
exporter.videoComposition = videoComposition
exporter.exportAsynchronously {
if exporter.status == .completed {
/*
Composition was successfully saved to the documents folder. Now create a new asset in the
device's camera roll (i.e. photo library):
*/
AssetLibrary.saveVideo(at: fileURL, completion: {
completion()
}, failure: {(error) in
failure(ProjectError.failedToExportToPhotoLibrary(detail: error?.localizedDescription ?? "Unknown"))
})
} else {
DispatchQueue.main.async {
failure(ProjectError.failedToExportComposition(detail: exporter.error?.localizedDescription ?? "Unknown"))
}
}
}
}
The WatermarkLayer class used above is defined as follows:
class WatermarkLayer: CATextLayer {
// MARK: - Constants
private let defaultFontSize: CGFloat = 48
private let rightMargin: CGFloat = 10
private let bottomMargin: CGFloat = 10
// MARK: - Initialization
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.")
}
// MARK: - CALayer
override func draw(in ctx: CGContext) {
let height = self.bounds.size.height
let fontSize = self.fontSize
//let yDiff = (height-fontSize)/2 - fontSize/10 // Center
let yDiff = (height-fontSize) - fontSize/10 - bottomMargin // Bottom (minus margin)
ctx.saveGState()
ctx.translateBy(x: -rightMargin, y: yDiff)
super.draw(in: ctx)
ctx.restoreGState()
}
}
What's going on? Which API is being "misused"?