# 像素流送流程

参考资料:

# 基本概念

像素流送可让虚幻在不可见的电脑上运行,利用该台电脑的资源(CPU,GPU,内存)来渲染每一帧,并不断地将此渲染输出编码到一个媒体流送中,再通过一个网页推送给用户,这个过程中:

  1. 像素流送并非播放预先录制的视频片段,而是播放虚幻引擎实时生成的渲染帧和音频。
  2. 用户可通过自己的浏览器对体验进行控制,将键盘、鼠标、触摸事件和播放器网页发出的自定义事件发送回虚幻引擎。

# 基本原理

STUNTURN

为了让信令服务器有能力在UE和浏览器之间搭建一个直接通道,每部分需要发送给其他人它自己的ip地址,这样浏览器能得到ue4使用的IP。反之亦然。

在一个本地网络中,每个终端通常假设其他终端能通过它的网卡上的私有IP来访问它。浏览器和UE4之间的互通在Internet上会跨越子网,或者是NAT(网络地址转换),但是这并不常见。相反地UE4和浏览器可以通过查询一个实现了STUN(Session Traversal Utilities for NAT)协议的服务器来找到他们的公开可见ip。虽然STUN服务器能告诉他们各自的公开可见ip,信令服务器依旧能提供直接连接。

你可以使用一个TURN服务器来分发UE4和浏览器之间的流媒体,TURN服务器通过TURN协议一方面连到UE4上,另一方面连到浏览器上。UE4应用将他的流媒体发给TURN服务器,TURN服务器再直接把这些数据给浏览器。在这个场景中,UE4应用和浏览器之间不是之间相连。如果你需要支持无线的移动设备,你只能使用TURN服务器,移动网络经常阻止客户端通过WebRTC协议连接。

新版本的UE4使用CoTURN,这是一个开源的STUN/TURN服务器。为了让流服务建立交互式连接,你需要为信令服务器的peerConnectionOptions配置中设置STUN/TURN服务器的主机名。此外,如果你想搭建自己的STUN/TURN服务器,你必须确保在peerConnectionOptions设置的IP地址和端口在网络上是可见的。

# 相关组件

这个过程会涉及3个组件:

  1. 像素流送插件:此插件在虚幻引擎中运行。其使用H.264视频压缩对每个渲染帧的最终结果进行编码,将这些视频帧随游戏音频一同打包到媒体流送中,并通过直接点对点连接将该流送发送到一个或多个连线的浏览器上。
  2. 信令和Web服务器:信令和Web服务器负责交涉浏览器和像素流送插件之间的连接,将播放媒体流送的HTML和JavaScript环境提供给浏览器。
  3. 配对服务器:各个客户端首先与配对服务器连接,而非需要与自身信令和Web服务器URL连接。配对服务器负责将所有请求程序重新指定到各自信令和Web服务器,以便在客户端与其自身UE4应用间构建对等网络连接。一旦激活连接,配对服务器不再会将任何新的浏览器连接重新定向至相同信令和Web服务器。

# 虚幻开启像素流

在虚幻的项目中开启像素流的步骤:

  1. 打开PixelStreaming插件。
  2. Edit > Project Settings > Engine > Input 面板下激活Always Show Touch Interface。
  3. Edit > Editor Preferences > Level Editor > Play 在 Additional Launch Parameters下可设置-AudioMixer。

# 像素流送方式

像素流送目前主要有两种方式:

  1. 多用户单实例
  2. 多用户多实例

# 多用户单实例

PixelStreaming1

该模式只运行一个虚幻引擎实例,将所有用户保存在同一个虚幻引擎会话中,但他们并未都能对会话进行控制。这种模式很像计算机课上老师向同学们远程直播操作,老师操作,学生只能看。这种模式一般适用于两种情景:

  1. 展示者在自己的浏览器中完全控制虚幻引擎,观看者只能观看。
  2. 创建不同用户的自定义控制设置,一起控制。比如有的人控制键盘,有的人控制鼠标。

通过不同用户请求不同的网页来进行操作权限控制。

实际操作:

  1. 运行信令服务器:

    .\Start_SignallingServer.ps1

  2. 运行虚幻程序:

    ./PixelStreamingDemo.exe -AudioMixer -PixelStreamingIP="127.0.0.1" -PixelStreamingPort="8888"

# 多用户多实例

PixelStreaming2

这种模式下所有用户能拥有独立的交互体验,每个用户在自己的虚幻实例中操作,这种情况下每个用户通过匹配服务器分配到自己的虚幻实例,信令服务器。

这种模式下配对服务器相当于一个前台,接收到请求后去找到未配对的信令服务器与客户对接。

实际操作,把下面的PublicIp换成信令服务器的外部IP:

  1. 进入Matchmaker文件夹,运行配对服务器:

    .\run.bat --HttpPort 80 --MatchmakerPort 9988

  2. 启动两个信令服务器:

    .\Start_SignallingServer.ps1 --UseMatchmaker true --MatchmakerAddress 127.0.0.1 --MatchmakerPort 9988 --PublicIp="10.0.100.10" --HttpPort 82 --StreamerPort 8800 --SFUPort 8861
    .\Start_SignallingServer.ps1 --UseMatchmaker true --MatchmakerAddress 127.0.0.1 --MatchmakerPort 9988 --PublicIp="10.0.100.10" --HttpPort 83 --StreamerPort 8801 --SFUPort 8862

  3. 启动两个虚幻实例:

    ./PixelStreamingDemo.exe -AudioMixer -PixelStreamingIP="127.0.0.1" -PixelStreamingPort="8800"
    ./PixelStreamingDemo.exe -AudioMixer -PixelStreamingIP="127.0.0.1" -PixelStreamingPort="8801"

注意:

  1. 启动虚幻的时候可使用-RenderOffScreen后台运行虚幻

# 使用demo

下面嵌入官方demo,基本原理是使用iframe。嵌入页面的逻辑:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <iframe src="http://192.168.10.226/" style="height: 600px; width:600px;" id="targetFrame"></iframe>
    <button id="btn_">
        向虚幻发送消息
    </button>
    
    <script>
        document.getElementById('targetFrame').onload = function () {
            let btn_ = document.getElementById('btn_')
            btn_.addEventListener('click',function(){
                console.log("向虚幻发送消息");
                //document.getElementById("targetFrame").contentWindow.window.methods.emitUIInteractionFun();
                
                const senddata=	{
                    "HandleName" : "CameraView",
                    "CameraType" : "Build", //
                    "BuildID" : "11", //参考对照表
                }

                document.getElementById("targetFrame").contentWindow.postMessage({ params: senddata}, "*")
            })


            function myHandleResponseFunction(data) {
                console.log("处理虚幻发过来的消息!");
                console.warn("主页面中的Response received!"+data);
            }

            
            window.onmessage = function (e) {
                console.log("主页面中接收到信息:"+e.data) 
                myHandleResponseFunction(e.data.params);
            }
        }
         
    </script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

player页面增加:

<script type="text/javascript">

 
 var originUrl="*";
 

 function handle_responses(data){
     console.warn("信令服务器中接收到数据:"+data);
     window.parent.postMessage({params:data},"*");
 }

 addResponseEventListener("handle_responses", handle_responses);

 

window.onmessage = function (e) {
     originUrl  = e.origin
     console.log("网页"+originUrl+"调用信令服务器方法:"+e.data);
     emitUIInteraction(e.data.params)    
     //e.source.postMessage(`测试数据`,  originUrl)    
 }

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 其他像素流方案