Capturing ARSCNView with virtual objects - iOS

518 Views Asked by At

I have an ARSCNView with virtual objects drawn. The virtual objects are drawn on the user's face. The session has the following configuration:

let configuration = ARFaceTrackingConfiguration()
configuration.worldAlignment = .gravityAndHeading

sceneView.session.run(configuration)

This ARSCNView is part of a video call. If we send back the pixel buffer like below,

 public func session(_ session: ARSession, didUpdate frame: ARFrame) {
    videoSource.sendBuffer(frame.capturedImage, timestamp: frame.timestamp)
 }

The virtual objects are not shown to my caller.

One of the things I tried is, to not rely on ARSessionDelegate's callback but use DispatchSourceTimer to send events.

func startCaptureView() {  
  // Timer with 0.1 second interval
  timer.schedule(deadline: .now(), repeating: .milliseconds(100))
  timer.setEventHandler { [weak self] in
    // Turn sceneView data into UIImage
    guard let sceneImage: CGImage = self?.sceneView.snapshot().cgImage else {
      return
    }

    self?.videoSourceQueue.async { [weak self] in
       if let buffer: CVPixelBuffer = ImageProcessor.pixelBuffer(forImage: sceneImage) {
             self?.videoSource.sendBuffer(buffer, timestamp: Double(mach_absolute_time()))
        }
    }
  }

  timer.resume()
}

The caller receives the data slowly with a choppy video experience and the images are not of the right size.

Any suggestions on how to send data about the virtual object along with the captured frame?

Reference: https://medium.com/agora-io/augmented-reality-video-conference-6845c001aec0

1

There are 1 best solutions below

0
Hermes On BEST ANSWER

The reason the Virtual objects are not appearing is because ARKit provides only the raw image, so frame.capturedImage is the image captured by the camera, without any of the SceneKit rendering. To pass the rendered video you will need to implement an offscreen SCNRenderer and pass the pixel buffer to Agora's SDK.

I would recommend you check out the Open Source framework AgoraARKit. I wrote the framework and it implements Agora.io Video SDK and ARVideoKit as dependancies. ARVideoKit is a popular library that implements an off-screen renderer and provides the rendered pixel buffer.

The library implements WorldTracking by default. If you want to extend the ARBroadcaster class to implement faceTracking you could use this code:

import ARKit

class FaceBroadcaster : ARBroadcaster {

    // placements dictionary
    var faceNodes: [UUID:SCNNode] = [:]           // Dictionary of faces

    override func viewDidLoad() {
        super.viewDidLoad() 
    }

    override func setARConfiguration() {
        print("setARConfiguration")        // Configure ARKit Session
        let configuration = ARFaceTrackingConfiguration()
        configuration.isLightEstimationEnabled = true
        // run the config to start the ARSession
        self.sceneView.session.run(configuration)
        self.arvkRenderer?.prepare(configuration)
    }

    // anchor detection
    override func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        super.renderer(renderer, didAdd: node, for: anchor)
        guard let sceneView = renderer as? ARSCNView, anchor is ARFaceAnchor else { return }
        /*
         Write depth but not color and render before other objects.
         This causes the geometry to occlude other SceneKit content
         while showing the camera view beneath, creating the illusion
         that real-world faces are obscuring virtual 3D objects.
         */
        let faceGeometry = ARSCNFaceGeometry(device: sceneView.device!)!
        faceGeometry.firstMaterial!.colorBufferWriteMask = []
        let occlusionNode = SCNNode(geometry: faceGeometry)
        occlusionNode.renderingOrder = -1

        let contentNode = SCNNode()
        contentNode.addChildNode(occlusionNode)
        node.addChildNode(contentNode)
        faceNodes[anchor.identifier] = node
    }
}