使用MTVerseXR SDK实现VR串流

1、概述

MTVerseXR SDK 是摩尔线程GPU加速的虚拟现实(VR)流媒体平台,专门用于从远程服务器流式传输基于标准OpenXR的应用程序。MTVerseXR可以通过Wi-Fi和USB流式将VR内容从Windows服务器流式传输到XR客户端设备, 使相对性能低的VR客户端可以使用高性能图形服务器的渲染能力。

2、环境准备

MTVerseXR SDK包括服务器驱动程序、客户端SDK和示例客户端应用程序。Windows上运行的服务端和VR头盔内的应用程序共同实现了Windows系统(边缘云)的SteamVR与VR头盔客户端的连接。

2.1 MTVerseXR 服务器驱动

运行平台要求

  • 系统:x86 CPU + Windows10

  • 显卡:摩尔线程MTT S80, MTT S70, 驱动程序 v240.50或更新版本

  • 软件:SteamVR

  • 基于OpenXR的Windows VR应用

2.2 MTVerseXR 客户端SDK

客户端SDK目前只支持Android系统。包含头文件、Android so库文件和资源文件,通过AAR的方式提供。

3、服务器驱动安装和启动

  1. 安装SteamVR:
  2. 安装运行时库
  3. 安装Vulkan驱动:
  4. 安装 MTVerseXR驱动:
    • 以管理员权限运行 MTVerseXRSetup-v1.0.0.exe 安装程序;
    • 按照安装向导的步骤进行安装,在最后一页,记得勾选"注册驱动"。如果未勾选注册驱动,可以通过以管理员权限运行安装目录下的 driver_install.bat 文件来完成注册;
    • 重启计算机
  5. 启动SteamVR
    • 确认MTVerseXR驱动启用, 查看SteamVR 【设置】-【启动/关闭】-【管理加载项】-【MTXR_VHMD】选项是开启状态(如果【设置】页面没有【启动/关闭】,点击【设置】页面的左下角【高级设置】下方的【显示】)

4、客户端开发指南

基于Client SDK 开发App的C++部分大致框架如下:

复制代码
int main() {
 
   MySetupGLES3-2
   MySetupXRSession
 
   MySetupDeviceDesc(&ddesc);
   MySetupCallbacks(&callbacks);
   MySetupReceiverDesc(&rdesc, ddesc, callbacks)
 
   MTXRCreateReceiver();
   MTXRConnect();
 
   while (!*exiting*) {
      MyPlatformEventHandling();
      if (client_state < connected) {
         MyRenderConnectionProgress();
      }
      else if (client_state == connected) {
         UpdateTrackingState();
         MTXRFetchFrame()
         MTXRRenderFrame(framesFetched);
         MTXRReleaseFrame()
      }
      else {
         MyHandleDisconnect();
         exiting = true;
      }
   }
 
   MTXRDestroyReceiver()
}

请注意,此处的UpdateTrackingState是伪代码中更新位姿回调的占位符。在UpdateTrackingState中,可以处理头盔、手柄的位姿和手柄事件,保存最新的位姿信息,在回调中更新。

Java部分需要在Activity的onCreate中调用MTXRClient.init 初始化客户端SDK组件。

SDK同时提供了工具类MTXRServerDiscover和MTXRUSB。其中MTXRServerDiscover用于探测局域网中的MTVerseXR Server,MTXRUSB用于打开USB配件模式的文件描述句柄,具体参考示例代码。

4.1 引用SDK包

在app的gradle中添加aar依赖。

手动或者使用gradle脚本解压aar文件后,在CMakeLists或者Android.mk中引用SDK的头文件和so文件。解压后头文件的路径为assets\include, so的路径为jni\arm64-v8a。

4.2 初始化

Java部分的初始化:

  • 在App MainActivity的onCreate中调用 MTXRClient.init 完成。

    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.xr);
    // 初始化MTXRClient
    MTXRClient.init(getBaseContext());

    复制代码
      // do super first, as that sets up some native things.
      super.onCreate(savedInstanceState);
    
      boolean usMic = false;
      enableMicrophone(usMic);
      ......

    }

C++部分初始化:

  1. OpenGLES 3.2上下文环境初始化(SDK目前仅支持OpenGLES环境下的显示,版本要求>=3.2);

  2. OpenXR Sessioin环境初始化(非必须通过OpenXR);

  3. 获取设备相关参数填充MTXRReceiverDesc的成员变量,包括:显示分辨率、刷新率、音频接收状态等;

  4. 设置MTXRClientCallbacks中需要的回调函数,包括GetTrackingState(必须实现以同步最新的位姿和手柄输入)和GetHapticCallback(手柄震动反馈);

  5. 调用MTXRCreateReceiver 创建Receiver对象,用于和服务器端通信的重要对象;

  6. 调用 MTXRConnect(Wifi连接)或者MTXRConnectUSB(USB连接) 连接服务器。

    void XRScene::Start(const std::string& serverIp, int fd, bool enableMic) {
    m_desc.deviceDesc.posePollFreq = 0;

    m_desc.clientCallbacks.GetTrackingState = [](void context, MTXRVRTrackingState trackingState) {
    reinterpret_cast<XRScene
    >(context)->GetTrackingState(trackingState);
    };
    m_desc.clientCallbacks.ReceiveMicrophoneData = NULL;
    m_desc.clientCallbacks.GetHapticCallback = [](void
    context, MTXRHapticFeedback haptic) {
    XRScene
    that = reinterpret_cast<XRScene*>(context);
    if (NULL != that->m_haptic_callback && NULL != that->m_context) {
    that->m_haptic_callback(that->m_context, haptic);
    }
    };
    ......
    m_desc.deviceDesc.receiveAudio = true;
    m_desc.clientContext = this;
    m_desc.requestedVersion = MTXR_VERSION_DWORD;
    m_desc.deviceDesc.sendAudio = enableMic;
    if (enableMic) {
    m_desc.clientCallbacks.ReceiveMicrophoneData = [](void* context, unsigned char* data, int size)->bool {
    return reinterpret_cast<XRScene*>(context)->ReceiveMicrophoneData(data, size);
    };
    }

    MTXRError ret = MTXRCreateReceiver(&m_desc, &m_receiver);
    if (MTXRError_Success == ret) {
    if (fd > 0) {
    MTXRConnectUSB(m_receiver, fd);
    } else {
    MTXRConnect(m_receiver, serverIp.c_str());
    }
    }
    }

4.3 主循环

MTVerseXR客户端App的主循环中需要根据当前的状态做出不同的行为。

  • Receiver未创建:显示一些交互UI,比如服务器的IP地址,让用户可以选择不同的连接方式。

  • 未连接到服务器:显示加载提示,告知用户正在连接服务器。

  • 成功连接到服务器:开始接收服务器传输的音视频数据。

  • 有可用的视频帧:判断当前是否有可用的视频帧,根据结果显示最新的视频或者缓存的视频帧。

  • 连接断开:用户主动断开连接后,应用需要清理相关资源。

渲染服务器视频流步骤:

  1. 获取可用视频帧:调用MTXRFetchFrame获取当前可用视频数据。

    void XRScene::BeginFrame(int64_t predictedDisplayTime, const MTXRVRTrackingState& pose, const std::vector& xrViews, bool showPerfUI) {
    {
    std::lock_guardstd::mutex guard(m_poseLock);
    m_xrState = pose;
    }

    ......
    m_xrFrame = std::make_shared();
    MTXRFetchFrame(m_receiver, m_xrFrame.get(), predictedDisplayTime, 0);
    }

  2. 渲染视频帧:MTXRFetchFrame成功后,调用MTXRRenderFrame显示当前最新视频帧,否则跳过绘制或者显示之前的缓存。注意需要在激活GLES 3.2上下文环境的渲染线程中调用。

    void XRScene::RenderFrame(const XrCompositionLayerProjectionView &layerView, int viewId) {
    MTXRPosef pose;
    pose.position = {layerView.pose.position.x, layerView.pose.position.y, layerView.pose.position.z};
    pose.orientation = {layerView.pose.orientation.x, layerView.pose.orientation.y, layerView.pose.orientation.z, layerView.pose.orientation.w};

    MTXRFovf fov = {layerView.fov.angleLeft, layerView.fov.angleRight, layerView.fov.angleUp, layerView.fov.angleDown};

    MTXRRenderFrame(m_receiver, m_xrFrame.get(), viewId == 0 ? MTXRFrameMask_Left : MTXRFrameMask_Right, &pose, &fov);
    }

    void RenderView(int viewID,
    XrTime predictedDisplayTime,
    std::shared_ptr xrScene,
    const XrCompositionLayerProjectionView &layerView,
    const XrSwapchainImageBaseHeader *swapchainImage,
    int64_t swapchainFormat) override {
    glBindFramebuffer(GL_FRAMEBUFFER, m_swapchainFramebuffer);

    const XrSwapchainImageOpenGLESKHR* swapchainImageGLES = reinterpret_cast<const XrSwapchainImageOpenGLESKHR *>(swapchainImage);
    const uint32_t colorTexture = swapchainImageGLES->image;

    glViewport(static_cast(layerView.subImage.imageRect.offset.x),
    static_cast(layerView.subImage.imageRect.offset.y),
    static_cast(layerView.subImage.imageRect.extent.width),
    static_cast(layerView.subImage.imageRect.extent.height));

    glFrontFace(GL_CW);
    glCullFace(GL_BACK);
    glDisable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);

    const uint32_t depthTexture = GetDepthTexture(colorTexture);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);

    // Clear swapchain and depth buffer.
    glClearColor(BackgroundColor[0], BackgroundColor[1], BackgroundColor[2], BackgroundColor[3]);
    glClearDepthf(1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    if (xrScene) {
    xrScene->RenderFrame(layerView, viewID);
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Swap our window every other eye for RenderDoc
    static int everyOther = 0;
    if ((everyOther++ & 1) != 0) {
    ksGpuWindow_SwapBuffers(&window);
    }
    }

  3. 释放视频帧:绘制完成后,需要调用MTXRReleaseFrame通知MTVerseXR SDK内部释放和回收视频资源。

    void XRScene::EndFrame() {
    MTXRReleaseFrame(m_receiver, m_xrFrame.get());
    m_xrFrame = nullptr;
    }

4.4 资源清理

退出客户端应用前,需要调用MTXRDestroyReceiver释放MTVerseXR的相关资源。

note

相关安装包、源码可以通过开发者社区下载:MTVerseXR SDK | 摩尔线程开发者)

相关推荐
陈天伟教授5 小时前
eDrawings 2026 x64 Edition-免费 3D 模型和 2D 工程图查看器
3d
郝学胜-神的一滴14 小时前
[简化版 GAMES 101] 计算机图形学 16:纹理走样、Mipmap、三线性插值、各向异性过滤原理全解
unity·游戏引擎·godot·图形渲染·three.js·opengl·unreal
Tian_Hang1 天前
eclipse ditto 学习笔记
运维·服务器·开发语言·javascript·3d
AI视觉网奇1 天前
BambuStudio 编译实战 2026
3d
AI前沿资讯1 天前
AI3D角色生产如何减少返工?用 V2Fun 前移建模与动画流程
人工智能·3d
蓝速科技2 天前
蓝速科技视觉 3D 全息舱 AI 数字人一体机带灯与无灯款深度评测
人工智能·科技·3d
尘中远2 天前
【Qwt 7.0 系列】3D 数据可视化 —— OpenGL 高性能三维绘图
qt·3d·qcustomplot·qwt·科学绘图·高性能绘图
ZJU_fish19962 天前
全局光照/阴影的几个常见问题
游戏引擎·图形渲染
林恒smileZAZ2 天前
Three.js 3D 地图特效与材质实现指南
javascript·3d·材质
蓝速科技2 天前
蓝速科技 3D 全息舱 AI 数字人博物馆导览效果实录
人工智能·科技·3d