# 像素流送流程
参考资料:
- 像素流送 (opens new window)
- 像素流示例项目 (opens new window)
- pixel-streaming (opens new window)
- UE4 Pixel Streaming 详细解读 (opens new window)
# 基本概念
像素流送可让虚幻在不可见的电脑上运行,利用该台电脑的资源(CPU,GPU,内存)来渲染每一帧,并不断地将此渲染输出编码到一个媒体流送中,再通过一个网页推送给用户,这个过程中:
- 像素流送并非播放预先录制的视频片段,而是播放虚幻引擎实时生成的渲染帧和音频。
- 用户可通过自己的浏览器对体验进行控制,将键盘、鼠标、触摸事件和播放器网页发出的自定义事件发送回虚幻引擎。
# 基本原理
为了让信令服务器有能力在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个组件:
- 像素流送插件:此插件在虚幻引擎中运行。其使用H.264视频压缩对每个渲染帧的最终结果进行编码,将这些视频帧随游戏音频一同打包到媒体流送中,并通过直接点对点连接将该流送发送到一个或多个连线的浏览器上。
- 信令和Web服务器:信令和Web服务器负责交涉浏览器和像素流送插件之间的连接,将播放媒体流送的HTML和JavaScript环境提供给浏览器。
- 配对服务器:各个客户端首先与配对服务器连接,而非需要与自身信令和Web服务器URL连接。配对服务器负责将所有请求程序重新指定到各自信令和Web服务器,以便在客户端与其自身UE4应用间构建对等网络连接。一旦激活连接,配对服务器不再会将任何新的浏览器连接重新定向至相同信令和Web服务器。
# 虚幻开启像素流
在虚幻的项目中开启像素流的步骤:
- 打开PixelStreaming插件。
- Edit > Project Settings > Engine > Input 面板下激活Always Show Touch Interface。
- Edit > Editor Preferences > Level Editor > Play 在 Additional Launch Parameters下可设置-AudioMixer。
# 像素流送方式
像素流送目前主要有两种方式:
- 多用户单实例
- 多用户多实例
# 多用户单实例
该模式只运行一个虚幻引擎实例,将所有用户保存在同一个虚幻引擎会话中,但他们并未都能对会话进行控制。这种模式很像计算机课上老师向同学们远程直播操作,老师操作,学生只能看。这种模式一般适用于两种情景:
- 展示者在自己的浏览器中完全控制虚幻引擎,观看者只能观看。
- 创建不同用户的自定义控制设置,一起控制。比如有的人控制键盘,有的人控制鼠标。
通过不同用户请求不同的网页来进行操作权限控制。
实际操作:
- 运行信令服务器:
.\Start_SignallingServer.ps1
- 运行虚幻程序:
./PixelStreamingDemo.exe -AudioMixer -PixelStreamingIP="127.0.0.1" -PixelStreamingPort="8888"
# 多用户多实例
这种模式下所有用户能拥有独立的交互体验,每个用户在自己的虚幻实例中操作,这种情况下每个用户通过匹配服务器分配到自己的虚幻实例,信令服务器。
这种模式下配对服务器相当于一个前台,接收到请求后去找到未配对的信令服务器与客户对接。
实际操作,把下面的PublicIp换成信令服务器的外部IP:
- 进入Matchmaker文件夹,运行配对服务器:
.\run.bat --HttpPort 80 --MatchmakerPort 9988
- 启动两个信令服务器:
.\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 - 启动两个虚幻实例:
./PixelStreamingDemo.exe -AudioMixer -PixelStreamingIP="127.0.0.1" -PixelStreamingPort="8800"
./PixelStreamingDemo.exe -AudioMixer -PixelStreamingIP="127.0.0.1" -PixelStreamingPort="8801"
注意:
- 启动虚幻的时候可使用-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>
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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23