UE5的PixelStreaming分析
本文分析UE5.1版本的PixelStreaming的Editor推流原理。 UE5.1相较于UE5.0在PixelStreaming的改动很大,引入了PixelStreamingEditor、PixelStreamingPlayer等功能。
使用
使用可以参考官方教程:https://docs.unrealengine.com/5.1/en-US/pixel-streaming-in-editor/。
Editor启动命令: 1
UnrealEditor-Cmd.exe -project Path\To\Your\Project.uproject -RenderOffscreen -EditorPixelStreamingRes=1920x1080 -EditorPixelStreamingStartOnLaunch=true -PixelStreamingURL=ws://127.0.0.1:8888
原理
P2P 建立
游戏客户端与信令服务器间的通信由WebSocket建立后,便在信令服务器协调下与Browser建立起P2P连接。建立的P2P连接共建立了音频、视频流通道和输入数据通道,此后PixelStream客户端和Browser之间的数据交互不在经由信令服务器。 P2P连接建立的过程如下图。
- PixelStreaming客户端启动会带上启动参数
-PixelStreamingURL=ws://127.0.0.1:8888
形式的信令服务器地址,。PS通过WebSocket连接信令服务器地址,信令服务器收到请求后会返回附带iceServers候选项的配置信息供后面使用。 - 用户在Browser访问WebServer,信令服务器收到用户请求后向PixelStreaming客户端发送playerConnected命令。
- PixelStreaming客户端收到playerConnected后,启动专门的线程处理与SignallingServer的操作,为player创建独立的session,创建 PeerConnectionFactory、创建PeerConnection、创建DataChannel、为Audio与Video添加track,创建Offer并设置Local SDP,将SDP发送给信令服务器。
- Browser收到经由信令服务器发送来的WebRTC Offer请求后,同样也创建PeerConnectionFactory、创建PeerConnection、创建DataChannel、为Audio与Video添加track,创建Answer并设置Local SDP,将Offer设置成Remote SDP,同时将Answer SDP发送给信令服务器。
- PixelStreaming客户端收到信令服务器发送来的Answer指令后设置Remote SDP。
- PixelStreaming客户端与Candidate在协商好STUN服务器后,便可开始P2P的通信。
P2P连接过程涉及几个关键类,包括: +
FPixelStreamingSignallingConnection
: 负责与Signalling
Server的连接、接收消息、发送消息。该类主要封装了对
IWebSocket
的调用,消息以Json格式传递。在
OnMessage()
函数中会解析信令服务器发送来的信息,解析出type字段,包括了config、offer、answer、iceCandidate、playerConnected、playerDisconnected、pong心跳信息等,后再去FStreamer对象处理。
+
FStreamer
:核心类,管理与信令服务器通信、管理用户会话,负责WebRTC的交互处理、UE输入、视频源等。
+
FPixelStreamingPeerConnection
:负责具体创建和处理PeerConnection一端的监听事件,包括创建
PeerConnectionFactory
、异步创建offer、answer异步接收offer、answer,异步设置LocalSDP和RemoteSDP,添加Remote
Ice Candidate,创建DataChannel。 +
FPixelStreamingDataChannel
:
负责处理DataChannel一端的监听事件(browser输入)和发送数据。
Editor的Video推流
Video推流流程分四个步骤:(1)捕获渲染帧,(2)创建编码器,(3)编码,(4)发送。
1. 捕获渲染帧
FullEditor模式捕获渲染帧通过UE中的Slate Render的回调函数
OnBackBufferReadyToPresent()
完成,传递的参数
SlateWindow
与 FrameBuffer
即为捕获到的窗口和渲染帧。
由于可视窗口可能会开多个且有叠加情况,该模式对所有可视化窗口帧做了组合。
组合原理:将可视化窗口从底到顶逐一处理,这样后处理的纹理能够覆盖前面的纹理。这里组合纹理大小是屏幕坐标+窗口大小,初始化的组合纹理长宽为1。
首先获取窗口坐标+大小,如果其长或宽有一方超过了组合纹理长或宽,则重新创建大纹理,将组合纹理赋值到此纹理;再将窗口纹理复制到组合纹理上。
这种做法简单粗暴,未作覆盖部分的裁剪,底层纹理仅保留露出来的纹理即可,该过程可在CPU端处理。
LevelEditor模式捕获渲染帧由
UGameViewportClient::OnViewportRendered
代理完成,该函数在viewport渲染完成后执行,拦截到的FViewport对象的RenderTargetTexture就是渲染帧。
回调函数在渲染线程中调用,捕获后的组合纹理会被拷贝到一个循环队列中缓存起来,等到对纹理编码时再从循环队列中读取。
纹理拷贝被封装到 FPixelCapture Plugin
中,由类
FPixelCaptureCapturer
完成,拷贝除了使用RHI外,又多了一种RDG方式。
纹理拷贝通过RHI异步拷贝,轮询Fence
来确定GPU拷贝完成;RDG方法不在GPU上立即执行Pass,而是先收集所有需要渲染的Pass,然后按照依赖的顺序对图表进行编译和执行,期间会执行各类裁剪和优化。
纹理缓存由 FOutputFrameBuffer
完成,内部实现为RingBuffer的循环队列。
2. 创建编码器
PixelStreaming使用 AVEncoder
组件对视频进行编码,选取的编码类型为H264。
AVEncoder::FVideoEncoderFactory::Get().Create()
可用于创建视频编码器工厂对象。先配置
VideoConfig,设置宽、高、码率、帧率等,根据 RHI (Vulkan3D113D12) 创建
VideoEncoderInput
,最终调用
VideoEncoderFactory
对象创建
encoder,此外还需要注册编码完成后的回调函数
SetOnEncodedPacket
。
首先是向WebRTC的 PeerConnectionFactory
传入自定义的
VideoEncoderFactory
。 在创建
PeerConnectionFactory
的函数
webrtc::CreatePeerConnectionFactory
的参数中会传入
AudioEncoderFactory
、 AudioDecoderFactory
、
VideoEncoderFactory
和 VideoDecoderFactory
四个编解码器工厂对象。 P2P创建 PeerConnectionFactory
传入的
AudioEncoderFactory
、 AudioDecoderFactory
都是WebRTC内部指定类,而 VideoEncoderFactory
传入的是
FVideoEncoderFactoryLayered
对象,VideoDecoderFactory
传入的是
FVideoDecoderFactory
对象。 接着,创建自定义的
VideoEncoder
。编码器的创建由WebRTC内部调用
FVideoEncoderFactoryLayered
对象的
CreateVideoEncoder()
方法,创建出了
FVideoEncoderSingleLayerH264
类型的
VideoEncoder
对象。 最后,完成对 AVEncoder
的创建。WebRTC在内部调用 FVideoEncoderSingleLayerH264
对象的 InitEncode
函数时,创建了封装AVEncoder的
FVideoEncoderWrapperHardware
对象,并在
AVEncoder::FVideoEncoder
的
SetOnEncodedPacket
回调函数中注册了
FVideoEncoderWrapperHardware:: OnEncodedPacket
以用于发送编码后的FrameBuffer。
3. 编码
编码过程发生在WebRTC的内部调用
FVideoEncoderSingleLayerH264
对象的 encode()
函数中,要将 WebRtc的 VideoFrame
转换成
TextureBuffer
并再次绑定到AVEncoder可以操作的
FVideoEncoderInputFrame
,进而调用AVEncoder的
encode()
函数进行真正的编码。
4. 发送
发送过程发生在 AVEncoder::FVideoEncoder
编码完成后的回调函数中,也就是函数
FVideoEncoderWrapperHardware::OnEncodedPacket()
。
由于发送利用的是WebRTC内部接口,因此需要将AVEncoder的编码后内容构建成WebRTC格式,最终再去调用
FVideoEncoderSingleLayerH264
的默认回调函数
OnEncodedImageCallback->OnEncodedImage()
来完成发送编码图像。