65.9K
CodeProject 正在变化。 阅读更多。
Home

增强现实视频会议

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2018年4月20日

CPOL
viewsIcon

10957

在本文中,我们将 ARKit 集成到视频会议场景中。

去年在 WWDC 2017 上,苹果推出了 ARKit。利用这项技术,开发者可以快速创建 iOS 平台上的混合现实应用程序,并使用设备的摄像头让增强现实变为现实。

在本文中,我们将 ARKit 集成到视频会议场景中。本文描述了视频中两个场景的实现

  • 将 ARKit 与实时视频流集成
  • 使用 Agora 的视频 SDK 将实时视频流渲染到 AR 平面上

我们将使用 ARKit 检测房间中的平面,然后使用 Agora.io Video SDK v2.1.1 中包含的 Custom Video Source 和 Renderer 功能,将实时视频流渲染到该平面上。这将为视频通话带来全息感,就像你在《星球大战》中看到的那样!此演示的源代码包含在文章的最后。只需将你的 Agora.io App ID 添加到 ViewController.swift 文件中,然后在你的设备上运行该应用!

在 AR 平面上渲染的视频流

基本 AR 准备

首先,我们将使用 ARKit 创建一个简单的平面感知应用程序作为开发基础。使用 Augmented Reality App 模板在 Xcode 中创建一个新项目,并选择 SceneKit 作为 Content Technology。

开始平面检测

在 ViewController 中将 ARConfiguration 设置为平面检测。

override func viewDidLoad() {
    super.viewDidLoad()
    sceneView.delegate = self
    sceneView.session.delegate = self
    sceneView.showsStatistics = true
}
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    let configuration = ARWorldTrackingConfiguration()
    configuration.planeDetection = .horizontal
    sceneView.session.run(configuration)
}

显示已识别的平面

要在已识别的平面上添加红色背景,请实现 ARSCNViewDelegate 回调方法 renderer:didAddNode:forAnchor

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else {
        return
    }
    let plane = SCNBox(width: CGFloat(planeAnchor.extent.x),
                height: CGFloat(planeAnchor.extent.y),
                length: CGFloat(planeAnchor.extent.z),
                chamferRadius: 0)
    plane.firstMaterial?.diffuse.contents = UIColor.red
    let planeNode = SCNNode(geometry: plane)
    node.addChildNode(planeNode)
    planeNode.runAction(SCNAction.fadeOut(duration: 1))
}

您现在已经完成了一个非常简单的 AR 应用程序。当环境中的平面被识别时,会在其上添加一个红色矩形并逐渐消失。

一旦平面被识别,就会出现一个红色矩形。

交互式广播准备

现在,我们将使用 Agora SDK 为应用添加实时视频通话功能。从官方网站下载 最新的 SDK 包 并将其添加到 Xcode 项目中。接下来,在 View Controller 中创建一个 AgoraRtcEngineKit 实例,并添加以下与实时视频相关的设置。

let agoraKit: AgoraRtcEngineKit = {
    let engine = AgoraRtcEngineKit.sharedEngine(withAppId:<#YourAppId#>, delegate: nil)
    engine.setChannelProfile(.liveBroadcasting)
    engine.setClientRole(.broadcaster)
    engine.enableVideo()
    return engine
}()

最后,在 viewDidLoad 函数中,将 agoraKit 的委托设置为视图控制器(self),然后加入 Agora 频道。

agoraKit.delegate = self
agoraKit.joinChannel(byToken: nil, channelId: "agoraar", info: nil, uid: 0, joinSuccess: nil)

此时,所有准备工作都已完成。我们有了一个可以识别平面并能进行音视频通话的 AR 应用程序。下一步是结合这两个功能。

广播 ARKit 屏幕

由于 ARKit 已经在使用设备摄像头,我们无法启动 AVCaptureSession 进行视频捕获。幸运的是,ARFrame 中的 capturedImage 接口为我们提供了摄像头捕获的图像。

添加自定义视频源

为了传输视频数据,我们需要创建一个类(ARVideoSource)并实现 AgoraVideoSourceProtocol,其中 bufferType 应返回 AgoraVideoBufferType

class ARVideoSource: NSObject, AgoraVideoSourceProtocol {
    var consumer: AgoraVideoFrameConsumer?
    func shouldInitialize() -> Bool { return true }
    func shouldStart() { }
    func shouldStop() { }
    func shouldDispose() { }
    func bufferType() -> AgoraVideoBufferType {
        return .pixelBuffer
    }
}

添加一个方法将视频帧传输到 ARVideoSource

func sendBuffer(_ buffer: CVPixelBuffer, timestamp: TimeInterval) {
    let time = CMTime(seconds: timestamp, preferredTimescale: 10000)
    consumer?.consumePixelBuffer(buffer, withTimestamp: time, rotation: .rotationNone)
}

接下来,在 View Controller 中实例化一个 ARVideoSource,并通过 viewDidLoad() 中的 setVideoSource 接口将实例变量传递给 Agora SDK。

let videoSource = ARVideoSource()
override func viewDidLoad() {
    ……
    agoraKit.setVideoSource(videoSource)
    ……
}

这使得只要我们调用 videoSource 的 sendBuffer:timestamp: 方法,就可以将视频帧传递给 Agora SDK。

发送相机数据

我们可以通过 ARSession 回调获取每个 ARFrame,从中读取相机数据,并使用 videoSource 发送出去。

viewDidLoad 方法中,将 ARSession 的委托设置为 View Controller 并添加回调函数。

override func viewDidLoad() {
    ……
    sceneView.session.delegate = self
    ……
}

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

发送 ARSCNView 数据

ARFrame 的 capturedImage 方法收集来自摄像机的原始数据。如果我们想发送已经添加了虚拟对象的图片,我们必须获取 ARSCNView 数据。这里有一个简单的想法:设置一个计时器,将 SCNView 转换为 UIImage,然后将其转换为 CVPixelBuffer,并提供给 videoSource。示例逻辑代码如下。

func startCaptureView() {
     //Timer with 0.1 second interval
     timer.schedule(deadline: .now(), repeating: .milliseconds(100))
     timer.setEventHandler { [unowned self] in
         // Turn sceneView data into UIImage
         let sceneImage: UIImage = self.image(ofView: self.sceneView)
         // Provide to Agora SDK after being converted to CVPixelBuffer
         self.videoSourceQueue.async { [unowned self] in
            let buffer: CVPixelBuffer = self.pixelBuffer(ofImage: sceneImage)
            self.videoSource.sendBuffer(buffer, timestamp: Double(mach_absolute_time()))
        }
    }
    timer.resume()
}

将实时流视频渲染到 AR 场景

添加虚拟显示

首先,我们需要创建一个用于渲染远程视频的虚拟显示器,并在用户点击时将其添加到 AR 场景中。

在 Storyboard 中向 ARSCNView 添加一个 UITapGestureRecognizer。当用户点击屏幕时,通过 ARSCNViewhitTest 方法获取平面的位置,并在点击位置放置一个虚拟显示器。

@IBAction func doSceneViewTapped(_ recognizer: UITapGestureRecognizer) {
     let location = recognizer.location(in: sceneView)
     guard let result = sceneView.hitTest(location, types: .existingPlane).first else {
         return
     }
     let scene = SCNScene(named: "art.scnassets/displayer.scn")!
     let rootNode = scene.rootNode
     rootNode.simdTransform = result.worldTransform
     sceneView.scene.rootNode.addChildNode(rootNode)
    let displayer = rootNode.childNode(withName: "displayer", recursively: false)!
    let screen = displayer.childNode(withName: "screen", recursively: false)!
    unusedScreenNodes.append(screen)
}

用户可以通过点击屏幕添加多个显示屏,它们会暂时保留在 unusedScreenNodes 数组中,直到它们被使用并渲染视频。

添加自定义视频渲染器

为了从 Agora SDK 获取远程视频数据,我们需要构建一个 ARVideoRenderer 对象,它实现 AgoraVideoSinkProtocol

class ARVideoRenderer: NSObject {
     var renderNode: SCNNode?
 }
 extension ARVideoRenderer: AgoraVideoSinkProtocol {
     func shouldInitialize() -> Bool { return true }
     func shouldStart() { }
     func shouldStop() { }
     func shouldDispose() { }
     func bufferType() -> AgoraVideoBufferType {
        return .rawData
    }
    func pixelFormat() -> AgoraVideoPixelFormat {
        return .I420
    }
    func renderRawData(_ rawData: UnsafeMutableRawPointer, size: CGSize, rotation: AgoraVideoRotation) {
        ……
    }
}

remoteRenderData:size:rotation: 方法可以获取远程视频数据,然后使用 Metal 框架渲染到 SCNNode。完整的 Metal 渲染代码可以在演示的最终版本中找到。

将自定义渲染器设置为 Agora SDK

通过实现 AgoraRtcEngineDelegate 协议的 rtcEngine:didJoinedOfUid:elapsed: 回调,可以识别流媒体加入频道的时机/位置。在回调中创建一个 ARVideoRenderer 实例,将虚拟屏幕节点(由用户之前点击屏幕时创建)设置为 ARVideoRenderer,并通过 setRemoteVideoRenderer:forUserId: 接口将自定义渲染器设置为 Agora SDK。

func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
    guard !unusedScreenNodes.isEmpty else {
        return
    }
    let screenNode = unusedScreenNodes.removeFirst()
    let renderer = ARVideoRenderer()
    renderer.renderNode = screenNode
    agoraKit.setRemoteVideoRenderer(renderer, forUserId: uid)
}

这样,当其他用户加入频道时,他们的视频就会显示在 AR 平面上,并获得虚拟会议室的效果。

通过使用 Agora SDK 的自定义视频源和自定义视频渲染器功能,可以轻松地结合 AR 和实时视频场景。此演示运行在 Agora SDK 上,使用 Agora 软件定义的实时网络,并支持 17 个同步视频流。这清楚地表明,AR 技术将为实时视频流带来全新的体验。

接下来的发展方向

  • 在《Pokemon Go》中挑战你的朋友
  • 在视频通话中让你的朋友/家人/同事与你更亲近
  • 创建一个混合现实健身应用程序,连接教练和他们的客户

完整的源代码,请在此处查看 Github 仓库 here

如果您有任何疑问,请随时通过我们的 开发者 Slack 频道 联系我们!如果您想加入我们的 Slack 社区,请填写此 表单,我们将发送邀请!

注册 Agora

© . All rights reserved.