Crash When Exporting Video with Text Overlay

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"?

I'm seeing the same crash in our video exporter under similar conditions. It only seems to happen when we use a AVVideoCompositionCoreAnimationTool in our export.

Some unusual details:
  • Currently, we only see this crash in the simulator.

    • We're not yet seeing it on devices.

  • So far we've duplicated the crash with the iOS 13.5 simulator and iOS 13.6 simulator.

    • The crash doesn't seem to happen in the iOS 13.0 simulator.

    • We haven't yet tested other simulator SDK's.


Code Block
Thread 71 Queue : com.apple.coremedia.basicvideocompositor.output (serial)
#0 0x00007fff522fe133 in _xpc_api_misuse ()
#1 0x00007fff522f855d in _xpc_shmem_create_with_prot ()
#2 0x00007fff260d03ec in _ioSurfaceClientCreate ()
#3 0x00007fff260cc3ac in -[IOSurface initWithProperties:] ()
#4 0x00007fff2b3b3e6f in CA::Render::Image::retain_iosurface() const ()
#5 0x00007fff2b401984 in CA::OGL::GLESContext::retain_image(CA::OGL::Image*, CA::Render::Texture*, bool) ()
#6 0x00007fff2b3ff780 in CA::OGL::GLESContext::bind_image_impl(unsigned int, CA::Render::Texture*, unsigned int, CA::OGL::TextureFilter, CA::OGL::TextureFilter, float, CA::OGL::TextureEdgeMode, float*, char const*) ()
#7 0x00007fff2b4059e6 in CA::OGL::Context::bind_image(unsigned int, CA::Render::Texture*, unsigned int, CA::OGL::TextureFilter, CA::OGL::TextureFilter, float, CA::OGL::TextureEdgeMode, CA::OGL::ContentsGeometry const*, float*, char const*) ()
#8 0x00007fff2b3eaff0 in CA::OGL::render_contents_background(CA::OGL::Renderer&, CA::OGL::Layer const*) ()
#9 0x00007fff2b4c187b in CA::OGL::LayerNode::apply(float, CA::OGL::Surface**, float*) ()
#10 0x00007fff2b4bf5d7 in CA::OGL::ImagingNode::render(CA::OGL::ImagingNode::RenderClosure*, unsigned int) ()
#11 0x00007fff2b3f01b9 in CA::OGL::render_layers(CA::OGL::Renderer&, CA::OGL::Layer*) ()
#12 0x00007fff2b4c191f in CA::OGL::LayerNode::apply(float, CA::OGL::Surface**, float*) ()
#13 0x00007fff2b4bf5d7 in CA::OGL::ImagingNode::render(CA::OGL::ImagingNode::RenderClosure*, unsigned int) ()
#14 0x00007fff2b3f01b9 in CA::OGL::render_layers(CA::OGL::Renderer&, CA::OGL::Layer*) ()
#15 0x00007fff2b4c191f in CA::OGL::LayerNode::apply(float, CA::OGL::Surface**, float*) ()
#16 0x00007fff2b4bf5d7 in CA::OGL::ImagingNode::render(CA::OGL::ImagingNode::RenderClosure*, unsigned int) ()
#17 0x00007fff2b3f01b9 in CA::OGL::render_layers(CA::OGL::Renderer&, CA::OGL::Layer*) ()
#18 0x00007fff2b3f0999 in CA::OGL::render_root_layers(CA::OGL::Renderer&, x_link_struct const*, CA::OGL::Gstate const&) ()
#19 0x00007fff2b3c4866 in CA::OGL::Renderer::render(CA::Render::Update const*, unsigned long) ()
#20 0x00007fff2b3c7383 in CA::OGL::render(CA::OGL::Renderer&, CA::Render::Update*, unsigned long) ()
#21 0x00007fff2b58e2c9 in -[CARenderer render] ()
#22 0x00007fff280e2b9d in FigCoreAnimationRendererCopyPixelBufferAtTime ()
#23 0x00007fff27ff1d5e in FigVCPCoreAnimationPostProcessorCopyPixelBufferAtTime ()
#24 0x00007fff2807f3ee in videoprocessor_frameCompleteCallback ()
#25 0x00007fff27ff3bda in fvcp_compositorHost_FrameCompletion ()
#26 0x00007fff28087b4a in __basicVideoCompositor_RenderFrame_block_invoke_2 ()
#27 0x00000001079c0f11 in _dispatch_call_block_and_release ()
#28 0x00000001079c1e8e in _dispatch_client_callout ()
#29 0x00000001079c86fd in _dispatch_lane_serial_drain ()
#30 0x00000001079c92c5 in _dispatch_lane_invoke ()
#31 0x00000001079d3399 in _dispatch_root_queue_drain ()
#32 0x00000001079d3168 in _dispatch_worker_thread ()
#33 0x00007fff522b7109 in _pthread_start ()
#34 0x00007fff522b2b8b in thread_start ()
Enqueued from com.apple.root.default-qos.overcommit (Thread 54) Queue : com.apple.root.default-qos.overcommit (serial)
#0 0x00000001079c5d46 in dispatch_async ()
#1 0x00007fff28085f85 in basicVideoCompositor_RenderFrame ()
#2 0x00007fff27ff3a13 in FigVCPCompositorHostComposeFrameAsync ()
#3 0x00007fff2807f08c in videoprocessor_processUntilHighWaterMet ()
#4 0x00007fff27f7ecbe in activitySchedulerOnThread ()
#5 0x00007fff24debe7a in figThreadMain ()
#6 0x00007fff522b7109 in _pthread_start ()
#7 0x00007fff522b2b8b in thread_start ()

The crash looks to be fairly deep in the CoreAnimation rendering code. I recommend filing a bug using the Feedback Assistant.
Filed feedback today: FB8137997.
Yep, same crash here. I'm adding a CALayer that animates its width.
Code Block
libxpc.dylib`_xpc_api_misuse:
  0x7fff522fe0b4 <+0>:  pushq %rbp
  0x7fff522fe0b5 <+1>:  movq  %rsp, %rbp
  0x7fff522fe0b8 <+4>:  pushq %rbx
  0x7fff522fe0b9 <+5>:  subq  $0xa8, %rsp
  0x7fff522fe0c0 <+12>: movq  %rdi, %r9
  0x7fff522fe0c3 <+15>: movaps 0xdc06(%rip), %xmm0    ; xpcVersionNumber + 160
  0x7fff522fe0ca <+22>: leaq  -0xb0(%rbp), %rbx
  0x7fff522fe0d1 <+29>: movaps %xmm0, 0x90(%rbx)
  0x7fff522fe0d8 <+36>: movaps %xmm0, 0x80(%rbx)
  0x7fff522fe0df <+43>: movaps %xmm0, 0x70(%rbx)
  0x7fff522fe0e3 <+47>: movaps %xmm0, 0x60(%rbx)
  0x7fff522fe0e7 <+51>: movaps %xmm0, 0x50(%rbx)
  0x7fff522fe0eb <+55>: movaps %xmm0, 0x40(%rbx)
  0x7fff522fe0ef <+59>: movaps %xmm0, 0x30(%rbx)
  0x7fff522fe0f3 <+63>: movaps %xmm0, 0x20(%rbx)
  0x7fff522fe0f7 <+67>: movaps %xmm0, 0x10(%rbx)
  0x7fff522fe0fb <+71>: movaps %xmm0, (%rbx)
  0x7fff522fe0fe <+74>: leaq  0x115ad(%rip), %r8    ; "XPC API Misuse: %s"
  0x7fff522fe105 <+81>: movl  $0xa0, %esi
  0x7fff522fe10a <+86>: movl  $0xa0, %ecx
  0x7fff522fe10f <+91>: movq  %rbx, %rdi
  0x7fff522fe112 <+94>: movl  $0x0, %edx
  0x7fff522fe117 <+99>: xorl  %eax, %eax
  0x7fff522fe119 <+101>: callq 0x7fff5230add8      ; symbol stub for: snprintf_chk
  0x7fff522fe11e <+106>: movq  %rbx, 0x37abe863(%rip)  ; gCRAnnotations + 8
  0x7fff522fe125 <+113>: leaq  0x11599(%rip), %rax    ; "API Misuse"
  0x7fff522fe12c <+120>: movq  %rax, 0x37abe85d(%rip)  ; gCRAnnotations + 16
-> 0x7fff522fe133 <+127>: ud2   

I'm only getting this issue on the iOS 13.6 simulator, not on a real device.
Did you ever get a response on this? I'm facing the same issue with video overlays on 14.0 simulator

Meet same issues, but on Simulator (Xcode 12.4 (12D4e)) only.

After some research, I found this crash is lead by AVVideoCompositionCoreAnimationTool's

+videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:inLayer:

And I fixed it by replacing it w/ one below (but need to handle instruction.layerInstructions in this way):

+videoCompositionCoreAnimationToolWithAdditionalLayer:asTrackID:

Below is a sample Objective-C code works on both real device & simulator:

...

// 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;

...

And btw, if you just need to add an image as watermark, another solution by using AVVideoComposition's

-videoCompositionWithAsset:applyingCIFiltersWithHandler:

also works well on both real device & simulator, but I tested it and found it's slower. Seems this way is more suitable for video blender/filter.

Just started seeing this today on Xcode 13 on Simulator, and tested on iOS 15 and 14.5 (both on Simulator). Cannot reproduce on device. This is super frustrating. Anyone have a solve for it yet.

Thanks @Kjuly

here is a Swift implementation based on your answer

 func merge(video videoPath: String, withForegroundImage foregroundImage: UIImage, completion: @escaping (URL?) -> Void) -> () {

        let videoUrl = URL(fileURLWithPath: videoPath)

        let videoUrlAsset = AVAsset(url: videoUrl)

        

        guard let videoTrack = videoUrlAsset.tracks(withMediaType: .video).first else { return }

        

        // Prepare watermark layer

        let imageLayer = CALayer()

        imageLayer.contents = foregroundImage.cgImage

        imageLayer.frame = CGRect(x: videoTrack.naturalSize.width - 300, y: 0.0, width: 300, height: 300)

        let unusedTrackID = videoUrlAsset.unusedTrackID()

        // !!! NOTE#01: Use as additional layer here instead of animation layer.

        let videoComposition = AVMutableVideoComposition(propertiesOf: videoUrlAsset)

        videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(additionalLayer: imageLayer, asTrackID: unusedTrackID)

        // Create video composition instruction

        let mutableInstrucitons = AVMutableVideoCompositionInstruction()

        mutableInstrucitons.timeRange = CMTimeRange(start: .zero, duration: videoUrlAsset.duration)

        // - Watermark layer instruction

        // !!! NOTE#02: Make this instruction track watermark layer by the `trackID`.

        let mutableLayerInstruction = AVMutableVideoCompositionLayerInstruction()

        mutableLayerInstruction.trackID = unusedTrackID

        // - Video track layer instruction

        let mutableVideoCompostionLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)

        // Watermark layer above video layer here.

        mutableInstrucitons.layerInstructions = [mutableLayerInstruction, mutableVideoCompostionLayerInstruction]

        videoComposition.instructions = [mutableInstrucitons]

        // Export to file

        let path = NSTemporaryDirectory().appending("mergedVideo.mp4")

        let destinationFilePath = URL.init(fileURLWithPath: path)

        do {

            if FileManager.default.fileExists(atPath: destinationFilePath.path) {

                try FileManager.default.removeItem(at: destinationFilePath)

                print("removed")

            }

        } catch {

            print(error)

        }

        let exportSession = AVAssetExportSession( asset: videoUrlAsset, presetName: AVAssetExportPresetHighestQuality)!

        exportSession.videoComposition = videoComposition

        exportSession.outputURL = destinationFilePath

        exportSession.outputFileType = AVFileType.mp4

        exportSession.exportAsynchronously { [weak exportSession] in

            if let strongExportSession = exportSession {

                completion(strongExportSession.outputURL!)

            }

        }

    }

}

I've intermittently had the same issue of the years depending on which iOS version apple put out. The issue "seemed" to go away in iOS 15.7 but in iOS 16+ it is intermittently back. I filled a feedback request with Apple with sample project, steps to reproduce, and video leading up to hang then crash. Fingers crossed that they finally fix this issue.

UPDATE: Also happens in 16.2 Beta (Oct 25, 2022) on all of my devices including iPhone 14 Pro Max.

Still same issues on iOS 16.2 simulator. The issue is with videoComposition. If I removed composition from AVAssetExportSession, it works.

Still the same error on iOS 17.0 simulator. When i make the layer fully opaque it works, but as soon as the layer has a slightly transparent element, it crashes with the same error.

Crash When Exporting Video with Text Overlay
 
 
Q