做者:龔宇華,聲網Agora.io 首席iOS研發工程師,負責iOS端移動應用產品設計和技術架構。
去年中旬,蘋果在 WWDC2017 推出了 ARKit。經過它,開發者能夠更加快速地在 iOS 平臺開發 AR 應用,利用鏡頭將虛擬照進現實。最近蘋果還加強了 iOS 系統對 ARKit的支持,並將加大對 AR 應用的推廣力度。node
在本篇中,咱們將會把 ARKit 融入視頻會議場景中。本文將會介紹視頻中兩種場景的實現:git
咱們將一塊兒在直播場景中利用 ARKit 實現平面檢測,還將應用到 Agora SDK 2.1 的新功能「自定義視頻源與渲染器」。若是你在此以前還未了解過 ARKit 的基本類及其原理,能夠先閱讀《上篇:ARKit 基礎知識》。github
很少說,先上效果圖。儘管與電影中看到的全息視頻會議效果還有距離,但你們能夠試着對後期效果優化無限接近電影場景(文末有源碼)。咱們在這裏僅分享利用 AR 在視頻會議中的實現技巧。數組
咱們首先使用ARKit建立一個簡單的識別平面的應用作爲開發基礎。session
在Xcode中使用 Augmented Reality App 模版建立一個新項目,其中 Content Technology 選擇 SceneKit.架構
在 ViewController 中設置 ARConfiguration 爲平面檢測。app
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:
,在識別出的平面上添加一個紅色的面。async
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 // 用模型生成 Node 對象並添加到識別出的平面上 let planeNode = SCNNode(geometry: plane) node.addChildNode(planeNode) // 漸隱消失 planeNode.runAction(SCNAction.fadeOut(duration: 1)) }
這樣就完成了一個最簡單的AR應用,當識別出環境中的平面時,會在上面添加一個紅色的矩形,並漸隱消失。ide
接下來咱們須要使用 Agora SDK 在應用中添加直播功能。優化
首先在官網下載最新的 SDK 包並添加到咱們的 Demo 中。接着在 ViewController 中添加 AgoraRtcEngineKit 的實例,而且進行直播相關的設置。
let agoraKit: AgoraRtcEngineKit = { let engine = AgoraRtcEngineKit.sharedEngine(withAppId: <#Your AppId#>, delegate: nil) engine.setChannelProfile(.liveBroadcasting) engine.setClientRole(.broadcaster) engine.enableVideo() return engine }()
最後在 viewDidLoad
方法中加入頻道。
agoraKit.delegate = self agoraKit.joinChannel(byToken: nil, channelId: "agoraar", info: nil, uid: 0, joinSuccess: nil)
至此,全部的準備工做都已經完成,咱們有了一個能夠識別平面的AR應用,同時又能夠進行音視頻通話,接下來要作的就是把這兩個功能結合起來。
由於 ARKit 已經佔用了設備攝像頭,咱們沒法本身啓動 AVCaptureSession 進行採集。幸虧 ARFrame
的 capturedImage
接口提供了攝像頭採集到的數據能夠供咱們直接使用。
爲了發送視頻數據,咱們須要構造一個實現了 AgoraVideoSourceProtocol
協議的類 ARVideoSource
。其中 bufferType 返回 AgoraVideoBufferTypePixelBuffer
類型。
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) }
接着在 ViewController 中實例化一個 ARVideoSource
, 並在 viewDidLoad 中經過 setVideoSource
接口設置給 Agora SDK
let videoSource = ARVideoSource() override func viewDidLoad() { …… agoraKit.setVideoSource(videoSource) …… }
這樣在咱們須要的時候,只要調用 videoSource 的 sendBuffer:timestamp:
方法,就能夠把視頻幀傳給 Agora SDK 了。
咱們能夠經過 ARSession 的回調拿到每一幀 ARFrame
,從中讀出攝像頭的數據,並使用 videoSource 發送出去。
在 viewDidLoad
中設置 ARSession 的回調
sceneView.session.delegate = self
實現 ARSessionDelegate
回調,讀取每一幀的攝像頭數據,並傳給 Agora SDK 。
extension ViewController: ARSessionDelegate { func session(_ session: ARSession, didUpdate frame: ARFrame) { videoSource.sendBuffer(frame.capturedImage, timestamp: frame.timestamp) } }
ARFrame
的 capturedImage
是攝像頭採集到的原始數據,若是咱們想發送的是已經添加好虛擬物體的畫面,那就只能本身獲取 ARSCNView
的數據了。這裏提供一種簡單的思路:設定一個定時器,定時去將 SCNView 轉爲 UIImage,接着轉換爲CVPixelBuffer,而後提供給 videoSource。下面只提供了示例邏輯代碼。
func startCaptureView() { // 0.1秒間隔的定時器 timer.schedule(deadline: .now(), repeating: .milliseconds(100)) timer.setEventHandler { [unowned self] in // 將 sceneView 數據變成 UIImage let sceneImage: UIImage = self.image(ofView: self.sceneView) // 轉化爲 CVPixelBuffer 後提供給 Agora SDK 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 場景中添加一個 SCNNode, 接着經過 Metal 把連麥對方的視頻數據渲染到 SCNNode 上。這樣便可實如今 AR 環境中顯示連麥端的畫面。
首先咱們須要建立用來渲染遠端視頻的虛擬顯示屏,並經過用戶的點擊添加到 AR 場景中。
在 Storyboard 中給 ARSCNView 添加一個 UITapGestureRecognizer
,當用戶點擊屏幕後,經過 ARSCNView
的 hitTest
方法獲得在平面上的位置,並把一個虛擬顯示屏放在點擊的位置上。
@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 獲取到遠端的視頻數據,咱們須要構造一個實現了 AgoraVideoSinkProtocol
協議的類型 ARVideoRenderer
。
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) { …… } }
經過 renderRawData:size:rotation:
方法能夠拿到遠端的視頻數據,而後就可使用 Metal 渲染到 SCNNode 上。具體的 Metal 渲染代碼能夠參考文末的完整版 Demo.
經過實現 AgoraRtcEngineDelegate
協議的 rtcEngine:didJoinedOfUid:elapsed:
回調,能夠獲得連麥者加入頻道的事件。在回調中建立 ARVideoRenderer
的實例,把前面用戶經過點擊屏幕建立的虛擬顯示屏 Node 設置給 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) }
這樣當連麥端加入頻道後,就會在虛擬顯示屏上顯示對方的視頻,獲得一個虛擬會議室的效果,正如咱們在文章開頭所看到的。
用最新 2.1 版 Agora SDK 的自定義視頻源和自定義視頻渲染器接口,能夠輕鬆地把 AR 和直播場景結合起來。Demo 基於 Agora SDK 以及 SD-RTN™ 運行,能夠支持17人的同時視頻連麥。能夠預見,AR 技術會爲實時視頻連麥帶來全新的體驗。
完整 Demo 請見 Github:https://github.com/AgoraIO/Ag...