1. 程式人生 > >AR+ 實時音視訊通話,×××無縫結合

AR+ 實時音視訊通話,×××無縫結合

今年中旬 Google 在萬眾期待下推出了 ARCore,能將現實與數碼完美無縫地融合在一起,豐富我們的現實世界。通過它開發者可以更加快速方便地在 Android 平臺開發 AR 應用,憑藉 AR 技術大量產品能找到新穎的應用場景,甚至開闢出新的一條產品線。

目前市場上已經有不少基於 AR 技術的產品,例如宜家家居的 IKEA Place 應用提供了新型的線上選購家俬方式,使用者只需要將手機攝像頭擺向想要放置傢俱的角落,接著選取你想要的傢俱,通過簡單的拖拉以及旋轉即可完成佈局,檢視這件傢俱是否符合你的心意。

下圖為使用 IKEA Place 的示意圖,看起來這張椅子還挺適合的 :)

IKEA Place 示意圖

那麼假如 AR 與其他技術進行結合,是否會有更激動人心的應用場景呢?

七牛實時音視訊雲 (以下簡稱七牛 RTN)基於已被廣泛標準化的 WebRTC 技術棧,有全平臺的相容性,支援各大瀏覽器如 Chrome、Safari、Firefox 以及各端 Android、iOS、Windows 等。強大的七牛實時音視訊流媒體網路在全球有 180 多個數據中心,具有強大的鏈路加速功能,豐富的節點保證了無論客戶分佈在全球的什麼地區都可以獲得加速。平均 200ms 的超低延時,為諸多對實時性有苛刻要求的客戶場景提供最根本支援,如一對一語聊、聊天室、視訊會議、線上教育等對互動性有強需求的場景均十分適合使用七牛 RTN。

在本篇中,我們會結合 Google 官方的示例 hello_ar_java

將 AR 技術融入到實時音視訊通話,其中會應用到 1.1.0+ 版本七牛 RTN SDK 的新功能 “外部音視訊資料匯入”。

以下為效果動圖

article_20181115_02

準備工作0: 整合七牛 RTN SDK 到 AR Demo

在真正開始編碼前,我們需要先將相應的專案和環境搭建完成

下載 七牛 RTN SDK 到當前目錄 QNRTC-Android

git clone [email protected]:pili-engineering/QNRTC-Android.git

下載 ARCore 到當前目錄 arcore-android-sdk

git clone [email protected]
:google-ar/arcore-android-sdk.git

拷貝相應七牛 RTN SDK 檔案到 hello_ar_java 工程中

  1. 將檔案 QNRTC-Android/releases/qndroid-rtc-1.2.0.jar 拷貝到 arcore-android-sdk/samples/hello_ar_java/app/libs/ 中(libs 目錄需要自行建立)
  2. QNRTC-Android/releases/ 下的 armeabi、armeabi-v7a、arm64-v8a、x86 等 4 個資料夾拷貝到 arcore-android-sdk/samples/hello_ar_java/app/src/main/jniLibs 資料夾中(jniLibs 目錄需要自行建立)
  3. 使用 AndroidStudio 開啟 arcore-android-sdk/samples/hello_ar_java 工程,修改其中幾項配置
    • 為了讓工程引用上面兩步中新增的庫,開啟 app/build.gradle 檔案,在 dependencies 中增加行 implementation fileTree(include: ['*.jar'], dir: 'libs')
    • 為了能進行實時通話,需要設定程式使用網路的許可權,開啟 AndroidManifest.xml 檔案,在 manifest 標籤中增加以下使用許可權宣告
      • <uses-permission android:name="android.permission.INTERNET"/>
      • <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

核心類介紹

在實際編碼與程式碼分析前,我們先簡單概述介紹其中會涉及到的核心類

QNRTCManager:七牛 RTN SDK 核心類,提供低延時實時音視訊通話能力

Session:ARCore 核心類,管理 AR 系統狀態包括攝像頭 Camera 採集、點網監測、平面檢測等能力

GLSurfaceView & Renderer:Android 系統提供的檢視類與渲染類,分別提供負責畫面顯示與渲染

BackgroundRenderer & ObjectRenderer & PlaneRenderer & PointCloudRenderer: Demo 中提供的渲染類,分別負責以下部分的渲染

  • 背景圖渲染(攝像頭預覽原始圖)
  • 物體及其陰影渲染(Android 模型及其陰影)
  • 平面渲染(AR 系統檢測到的平面)
  • 點雲渲染(AR 系統檢測到的點雲)

準備工作1: 建立基本的實時音視訊通話環境

首先需要實現實時音視訊的房間事件監聽器 QNRoomEventListener,其需要實現的方法很多,以下只展現這次簡單示例需要用到的方法,完整的介面說明在這裡

public class HelloArActivity extends AppCompatActivity implements GLSurfaceView.Renderer, QNRoomEventListener {
    private boolean mPublished = false; // 標識本地是否釋出成功

    ...

    @Override
    public void onJoinedRoom() {
        mRTCManager.publish(); // 加入房間成功後,嘗試釋出
    }

    @Override
    public void onLocalPublished() {
        mPublished = true; // 釋出成功後,標識為 true
    }

    ...
}

onCreate 方法尾部初始化實時音視訊通話環境並加入指定房間,其中關於 Room Token 獲取的方式可以參考這裡

protected void onCreate(Bundle savedInstanceState) {
    ...
    QNRTCSetting setting = new QNRTCSetting();
    setting.setExternalVideoInputEnabled(true); // 開啟外部視訊匯入功能

    mRTCManager.setRoomEventListener(this); // 設定房間事件監聽器
    mRTCManager.initialize(this, setting); // 七牛 RTN SDK 初始化

    mRTCManager.joinRoom(###Your Room Token###); // 通過 Room Token 加入指定房間
}

準備工作2: 建立基本的 AR 環境

利用 GLSurfaceView & Renderer 為繪製 AR 畫面做好準備

在 Activity 類宣告中實現 GLSurfaceView.Renderer 介面,在本 Demo 中如下,隨即需要我們實現 3 個相應的方法,意義分別在註釋中被描述

public class HelloArActivity extends AppCompatActivity implements GLSurfaceView.Renderer, QNRoomEventListener {
    /**
     * 顯示 Surface 建立完成時回撥
    **/
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    }

    ...

   /**
     * 顯示 Surface 尺寸大小改變時回撥
    **/
    public void onSurfaceChanged(GL10 gl, int width, int height) {
    }

    ...

    /**
     * 顯示 Surface 建立完成時回撥
    **/
    public void onDrawFrame(GL10 gl) {
    }
}

在實現了 Renderer 渲染類後,我們需要提供用作顯示的 Surface,以便讓 Renderer 在其上進行渲染顯示,GLSurfaceView 就有這種能力。

以下示例程式碼,從佈局 xml 檔案中解析出 GLSurfaceView 並設定 Renderer

surfaceView = findViewById(R.id.surfaceview); // 從佈局 xml 中解析 GLSurfaceView
...
surfaceView.setRenderer(this); // 設定 Renderer

建立 Session

Session 是 AR 系統的主入口類,在任何 AR 操作前必須先初始化並啟動

protected void onResume() {
    session = new Session(/* context= */ this); // AR 系統初始化
    ...
    session.resume(); // 開始 AR 會話,嘗試開啟攝像頭,如攝像頭被佔用,會丟擲 CameraNotAvailableException 異常
}

使用 OpenGL Shader 在顯示 Surface 上繪製 AR 增強畫面

在 AR 會話開始後,攝像頭的每一幀資料都能提供以下資訊

  • 原始攝像頭預覽資料
  • 檢測到的平面陣列
  • 檢測到的點雲陣列
  • 平面觸控事件

我們可以在 onDrawFrame 方法中利用以上的事件進行相應的處理,例如遇到平面觸控事件,則在相應的位置放上一個 Android 模型,並且同時繪製出檢測到的平面以及點雲。

// 繪製背景
private final BackgroundRenderer backgroundRenderer = new BackgroundRenderer();
// 繪製物體
private final ObjectRenderer virtualObject = new ObjectRenderer();
// 繪製物體陰影
private final ObjectRenderer virtualObjectShadow = new ObjectRenderer();
// 繪製平面
private final PlaneRenderer planeRenderer = new PlaneRenderer();
// 繪製雲點
private final PointCloudRenderer pointCloudRenderer = new PointCloudRenderer();

public void onDrawFrame(GL10 gl) {
    frame = session.update(); // 獲取攝像頭原始資料幀(阻塞方法)

    // Handle one tap per frame.
    handleTap(frame, camera); // 檢測是否有平面點選事件,如有則在相應位置放置 Android 模型

    ...
    // Draw background.
    backgroundRenderer.draw(frame); // 將攝像頭預覽資料作為背景圖繪製

    ...
    // Visualize tracked points.
    PointCloud pointCloud = frame.acquirePointCloud();
    pointCloudRenderer.update(pointCloud);
    pointCloudRenderer.draw(viewmtx, projmtx); // 繪製點雲

    ...
    // Visualize planes.
    planeRenderer.drawPlanes(session.getAllTrackables(Plane.class), camera.getDisplayOrientedPose(), projmtx); // 繪製平面

    ...
    // Update and draw the model and its shadow.
    virtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
    virtualObjectShadow.updateModelMatrix(anchorMatrix, scaleFactor);
    virtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, coloredAnchor.color); // 繪製 Android 模型
    virtualObjectShadow.draw(viewmtx, projmtx, colorCorrectionRgba, coloredAnchor.color); // 繪製 Android 模型的陰影
}

##技術結合: 將 AR 增強畫面釋出到實時音視訊雲

在分別實現了基本的 實時音視訊通話AR 增強畫面 後,現在只需要將它們進行最後的結合。

因為 Session 啟動後會佔用裝置攝像頭,因此七牛 RTN SDK 無法進行採集,這時候我們需要使用最新版本提供的功能 ”外部音視訊資料匯入“。

在釋出流前,我們需要獲取到 AR 增強畫面 的 NV21 格式資料,因為當前七牛 RTN Android SDK 的 “外部視訊資料匯入” 功能只支援 NV21 格式的資料。

以下示例程式碼在 onDrawFrame 方法中的尾部新增,將 GLSurfaceView 的 Surface 內容資料讀取出來,進行必要的格式轉換,接著釋出出去

public void onDrawFrame(GL10 gl) {
    ...

    if (mPublished) { // 只在七牛 RTN 釋出流成功後才匯入 AR 資料
        // 將 AR 增強畫面 的資料從 GPU 中讀取出來
        GLES20.glReadPixels(0, 0, mSurfaceWidth, mSurfaceHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mBufferRGBA);

        // RGBA 轉為 NV21(篇幅原因,不在此展開演算法)
        mBufferNV21 = RGBAToNV21(mBufferRGBA, mSurfaceWidth, mSurfaceHeight);

        // 通過 "外部視訊資料匯入" 功能將 NV21 資料形式的 AR 增強畫面 釋出出去
        mRTCManager.inputVideoFrame(mBufferNV21, mSurfaceWidth, mSurfaceHeight, 0, frame.getTimestamp());
    }
}

總結

使用 1.1.0+ 版本七牛 RTN SDK 提供的 “外部音視訊資料匯入” 功能,可以輕鬆地把 AR 與實時音視訊通訊結合起來。以上程式基於七牛 RTN SDK 以及相應的 RTN 網路執行,最大可以支援 20 人同時低延時音視訊通話。相信不久將來 AR 技術與實時音視訊通訊的結合會帶來更多的應用場景。