
去年我接手了一個(gè)云端交互展示項(xiàng)目需要在網(wǎng)頁(yè)里直接看到虛幻引擎渲染的實(shí)時(shí)場(chǎng)景——用戶不裝客戶端、不裝UE、不下載任何素材打開(kāi)瀏覽器就能操作。技術(shù)選型幾乎沒(méi)什么懸念UE自帶Pixel Streaming像素流送。但真正讓我頭疼的是部署環(huán)境項(xiàng)目要求放到Linux服務(wù)器上而網(wǎng)上能找到的教程和案例十有八九都是在Windows上跑通的。我前后折騰了將近兩周經(jīng)歷了信令連不上、無(wú)頭渲染黑屏、NVENC編碼器不工作、瀏覽器鼠標(biāo)鍵盤(pán)全部失靈等一系列問(wèn)題。這篇文章就圍繞“虛幻引擎Linux像素流送部署”這件事把完整的部署鏈路、踩坑清單、性能調(diào)優(yōu)和容器化思路都整理出來(lái)。無(wú)論你是要給客戶做云端展廳還是想把自己做的策略游戲搬到瀏覽器里遠(yuǎn)程試玩這套流程都能直接拿來(lái)用。1. 為什么要把像素流送挪到Linux上跑收益與代價(jià)的賬要算清很多團(tuán)隊(duì)第一反應(yīng)是“在Windows服務(wù)器上跑不就完了嗎”畢竟官方文檔、示例工程、同行經(jīng)驗(yàn)絕大多數(shù)都是Windows向的。這個(gè)想法沒(méi)毛病但一旦你開(kāi)始考慮生產(chǎn)環(huán)境Linux幾乎是被逼著選的。1.1 云服務(wù)器選型帶來(lái)的真金白銀差距像素流送的本質(zhì)是UE進(jìn)程在服務(wù)器上實(shí)時(shí)渲染然后把畫(huà)面編碼推給瀏覽器。它非常吃GPU但不吃Windows專(zhuān)有的東西。國(guó)內(nèi)主流云廠商的GPU實(shí)例Windows license普遍要額外收費(fèi)同樣的卡型配置Linux實(shí)例便宜不少。而且很多高性能實(shí)例的默認(rèn)鏡像就是Ubuntu或CentOS系你選Windows反而要多等半小時(shí)初始化。更關(guān)鍵的是擴(kuò)展方式。像素流送要支撐大量并發(fā)用戶常見(jiàn)做法是開(kāi)多個(gè)UE實(shí)例每個(gè)實(shí)例負(fù)責(zé)一路或幾路串流。Linux下一條命令就能拉起幾十個(gè)進(jìn)程配合systemd或者容器編排工具做自動(dòng)擴(kuò)縮容非常順手。在Windows上做同樣的事你要面對(duì)遠(yuǎn)程桌面沖突、服務(wù)啟動(dòng)依賴(lài)、輸入會(huì)話占用這些亂七八糟的問(wèn)題運(yùn)維成本高一個(gè)量級(jí)。1.2 代價(jià)清單編譯耗時(shí)長(zhǎng)、調(diào)試手段少但Linux部署絕對(duì)不是零成本。我踩過(guò)的坑大致可以列成這張清單問(wèn)題類(lèi)型具體表現(xiàn)影響程度Linux打包需要額外安裝交叉編譯工具鏈打包耗時(shí)比Windows長(zhǎng)中無(wú)頭渲染服務(wù)器沒(méi)有顯示器UE默認(rèn)創(chuàng)建窗口會(huì)失敗必須開(kāi)離屏渲染高顯卡驅(qū)動(dòng)編碼器依賴(lài)NVIDIA驅(qū)動(dòng)和CUDA裝錯(cuò)版本就沒(méi)法硬編高信令服務(wù)器自簽證書(shū)、Node版本都會(huì)導(dǎo)致連接失敗中輸入控制瀏覽器Pointer Lock在iframe場(chǎng)景下經(jīng)常失效低音頻回傳無(wú)桌面環(huán)境下沒(méi)有音頻設(shè)備串流聲音直接缺失低你如果只是本地開(kāi)發(fā)機(jī)上臨時(shí)驗(yàn)證一下這些都不是事。但一旦上了生產(chǎn)每一行都能卡住你半天。1.3 哪些項(xiàng)目適合Linux部署哪些反而留在Windows從我實(shí)際接觸的項(xiàng)目來(lái)看適合Linux部署的有這么幾類(lèi)云端展廳/云渲染用戶量大、需要并發(fā)擴(kuò)容的給外部客戶提供試用Demo希望對(duì)方不下載任何文件、打開(kāi)即玩的需要和現(xiàn)有CI/CD體系做集成的因?yàn)長(zhǎng)inux環(huán)境天然適合自動(dòng)化流水線多實(shí)例管理、需要監(jiān)控和自動(dòng)重啟的長(zhǎng)駐服務(wù)。反過(guò)來(lái)如果你的項(xiàng)目還處于“給老板演示一下效果”的階段或者只有一臺(tái)Windows工作站那就不用折騰Linux了先在Windows上把功能跑通再說(shuō)。Linux部署解決的是“穩(wěn)定承載流量”的問(wèn)題不是“實(shí)現(xiàn)功能”的問(wèn)題。2. Linux環(huán)境準(zhǔn)備與UE工程編譯一次配好少走三輪彎路很多人拿到UE工程后第一件事就是拷到Linux服務(wù)器上直接編譯結(jié)果各種報(bào)錯(cuò)。這一章把環(huán)境和編譯的事說(shuō)透。2.1 基礎(chǔ)依賴(lài)少裝一個(gè)庫(kù)編譯期現(xiàn)形在Ubuntu 20.04上我用的依賴(lài)安裝命令如下sudo apt-get update sudo apt-get install build-essential \ libx11-dev libxrandr-dev libxinerama-dev \ libxcursor-dev libxi-dev libgl1-mesa-dev \ libfontconfig1-dev libssl-dev libasound2-dev這些都是UE在Linux上編譯和運(yùn)行需要的基礎(chǔ)庫(kù)。最容易漏掉的是libx11-dev這一組X11開(kāi)發(fā)頭文件如果沒(méi)裝編譯到一半會(huì)報(bào)X11/Xlib.h: No such file or directory。這個(gè)報(bào)錯(cuò)很容易被誤判成代碼問(wèn)題實(shí)際上就是缺庫(kù)。另外UE自帶的構(gòu)建腳本對(duì)LLVM/Clang有版本要求如果你是從UE源碼編譯引擎建議直接用引擎要求的LLVM版本不要圖省事裝系統(tǒng)的clang否則鏈接階段會(huì)出一堆莫名其妙的符號(hào)錯(cuò)誤。如果只是部署現(xiàn)成工程而不是編譯引擎那主要保證系統(tǒng)庫(kù)齊全就行。2.2 Windows上打好Linux包再傳服務(wù)器實(shí)際部署時(shí)我推薦的工作流是在Windows開(kāi)發(fā)機(jī)上用UE編輯器直接打包Linux版本然后把整個(gè)Linux文件夾傳到服務(wù)器上。這樣Linux服務(wù)器上不需要裝UE編輯器也不需要安裝Epic相關(guān)工具鏈?zhǔn)〉粢淮蟀氕h(huán)境問(wèn)題。具體操作很簡(jiǎn)單在Windows上用UE編輯器打開(kāi)項(xiàng)目選擇File - Package Project - Linux目標(biāo)平臺(tái)如果沒(méi)有Linux選項(xiàng)先到Edit - Project Settings - Platforms里勾選Linux并重啟編輯器打包完成后把輸出目錄里的項(xiàng)目名可執(zhí)行文件以及Engine相關(guān)目錄一起傳到Linux服務(wù)器。這里有個(gè)容易忽略的點(diǎn)打包產(chǎn)物不是單文件里面包含了一整個(gè)Engine運(yùn)行時(shí)一堆.so共享庫(kù)和資源傳輸?shù)臅r(shí)候要整個(gè)目錄一起傳別只傳那個(gè)可執(zhí)行文件。我見(jiàn)過(guò)有同事只拷了可執(zhí)行文件跑起來(lái)直接報(bào)缺庫(kù)。2.3 編譯期常見(jiàn)的坑路徑、權(quán)限和鏈接錯(cuò)誤Linux打包過(guò)程中最常見(jiàn)的報(bào)錯(cuò)有這么幾類(lèi)路徑含中文或空格UE在Linux交叉編譯時(shí)對(duì)路徑比較敏感工程路徑和輸出路徑都盡量全英文。沒(méi)有執(zhí)行權(quán)限傳到服務(wù)器后記得chmod x給可執(zhí)行文件加權(quán)限否則Permission denied會(huì)讓人誤以為依賴(lài)問(wèn)題。glibc版本不匹配如果你在Ubuntu 22.04上跑UE 5.4的包大概率沒(méi)問(wèn)題但如果你的跑系統(tǒng)是CentOS 7glibc 2.17基本跑不動(dòng)新版UE。生產(chǎn)環(huán)境建議直接用Ubuntu 20.04或22.04省心。還有個(gè)小技巧加參數(shù)-log啟動(dòng)UE日志會(huì)直接打到標(biāo)準(zhǔn)輸出方便你排查啟動(dòng)階段的問(wèn)題。后面章節(jié)會(huì)反復(fù)用到這個(gè)參數(shù)。3. 信令、連接與會(huì)話像素流送握手的完整鏈路在Linux上部署像素流送一半以上的時(shí)間會(huì)耗在“信令連不上”這件事上。要理解為什么連不上得先理解像素流送的握手過(guò)程。3.1 信令服務(wù)器默認(rèn)在做什么UE像素流送的架構(gòu)分三層UE渲染進(jìn)程、信令服務(wù)器、瀏覽器前端頁(yè)面。UE進(jìn)程負(fù)責(zé)渲染和編碼是一臺(tái)“推流機(jī)器”信令服務(wù)器SignallingWebServer負(fù)責(zé)協(xié)調(diào)UE和瀏覽器之間的連接瀏覽器前端用戶看到的交互頁(yè)面。UE工程打包后像素流送插件目錄下會(huì)帶一個(gè)SignallingWebServer它是基于Node.js寫(xiě)的。它的核心職責(zé)是當(dāng)用戶在瀏覽器里點(diǎn)擊“開(kāi)始”時(shí)把這個(gè)請(qǐng)求轉(zhuǎn)發(fā)給UE進(jìn)程然后在兩者之間傳遞WebRTC建立連接所需的SDP和ICE信息。這個(gè)服務(wù)器有兩種流量一種是HTTP負(fù)責(zé)把前端頁(yè)面HTML/JS發(fā)給瀏覽器另一種是WebSocket負(fù)責(zé)轉(zhuǎn)發(fā)信令。所以默認(rèn)情況下你會(huì)看到兩個(gè)端口HTTP端口通常是80新版也有用其他端口的情況和WebSocket端口默認(rèn)8888。瀏覽器先通過(guò)HTTP加載頁(yè)面再通過(guò)WebSocket建立信令通道。3.2 握手過(guò)程信令、SDP、ICE、WebRTC完整的連接流程是這樣的瀏覽器通過(guò)HTTP向信令服務(wù)器請(qǐng)求頁(yè)面頁(yè)面加載完成后前端JS和信令服務(wù)器建立WebSocket連接用戶點(diǎn)擊Play按鈕前端通過(guò)WebSocket發(fā)送一個(gè)“我要開(kāi)始連接”的消息信令服務(wù)器把這個(gè)消息轉(zhuǎn)發(fā)給UE進(jìn)程UE進(jìn)程收到請(qǐng)求后創(chuàng)建WebRTC PeerConnection生成SDP Offer再把Offer通過(guò)信令服務(wù)器回傳給瀏覽器瀏覽器收到Offer后生成SDP Answer回傳雙方開(kāi)始通過(guò)ICE進(jìn)行網(wǎng)絡(luò)路徑協(xié)商找到一條可用的P2P通道協(xié)商成功后音視頻數(shù)據(jù)直接通過(guò)WebRTC的SRTP通道傳輸信令服務(wù)器只負(fù)責(zé)后續(xù)的控制消息比如鼠標(biāo)鍵盤(pán)輸入。理解這個(gè)流程對(duì)排查問(wèn)題特別重要。比如“點(diǎn)擊Play后頁(yè)面一直轉(zhuǎn)圈”問(wèn)題可能出在WebSocket沒(méi)建立成功也可能出在UE進(jìn)程的SDP沒(méi)有回傳如果頁(yè)面提示“ICE failed”那就是網(wǎng)絡(luò)路徑協(xié)商沒(méi)完成需要檢查UDP端口和STUN配置。后面會(huì)逐一展開(kāi)。3.3 最容易被忽略的自簽證書(shū)與“拒絕不安全連接”這個(gè)坑幾乎每個(gè)新手都會(huì)踩。像素流送自帶的信令服務(wù)器默認(rèn)使用HTTPS配套的證書(shū)是自簽的。你用Chrome打開(kāi)頁(yè)面時(shí)地址欄會(huì)提示“不安全”如果你不點(diǎn)“高級(jí) - 繼續(xù)前往”頁(yè)面就不會(huì)正常加載WebSocket也會(huì)被瀏覽器安全策略攔截。在本地測(cè)試時(shí)很多人直接點(diǎn)了“繼續(xù)前往”沒(méi)當(dāng)回事。但是到了生產(chǎn)環(huán)境如果頁(yè)面被嵌入到iframe里或者需要通過(guò)域名訪問(wèn)自簽證書(shū)引起的麻煩會(huì)無(wú)限放大。我的建議是如果只是內(nèi)部測(cè)試忍一下繼續(xù)訪問(wèn)就行如果要公網(wǎng)使用把信令服務(wù)器的證書(shū)換成Lets Encrypt或者公司簽發(fā)的正式證書(shū)否則用戶根本進(jìn)不來(lái)。4. 啟動(dòng)參數(shù)與首批驗(yàn)證十分鐘讓瀏覽器出畫(huà)面環(huán)境和信令都搞定后最關(guān)鍵的時(shí)刻就到了啟動(dòng)UE進(jìn)程打開(kāi)瀏覽器看能不能出畫(huà)面。4.1 最小可用啟動(dòng)命令在Linux服務(wù)器上我用過(guò)的最穩(wěn)啟動(dòng)命令長(zhǎng)這樣xvfb-run -a -s -screen 0 1920x1080x24 \ ./MyProject -RenderOffScreen \ -PixelStreamingIP127.0.0.1 \ -PixelStreamingPort8888 \ -ResX1920 -ResY1080 -FullScreen拆開(kāi)解釋一下。xvfb-run是X虛擬幀緩沖工具相當(dāng)于給UE一個(gè)虛擬顯示器。我知道有人會(huì)說(shuō)-RenderOffScreen不就是離屏渲染嗎為什么還要xvfb實(shí)測(cè)結(jié)果是UE的Pixel Streaming在初始化RHI圖形接口時(shí)仍然需要一個(gè)顯示環(huán)境特別是Vulkan模式?jīng)]有X server會(huì)直接崩潰。xvfb-run就是為了解決這個(gè)問(wèn)題的。-RenderOffScreen告訴UE不創(chuàng)建窗口、直接在后臺(tái)渲染。-PixelStreamingIP和-PixelStreamingPort指定信令服務(wù)器的地址和端口。如果UE和信令服務(wù)器在同一臺(tái)機(jī)器上寫(xiě)127.0.0.1就行如果分開(kāi)部署這里要改成信令服務(wù)器的實(shí)際內(nèi)網(wǎng)IP。-ResX和-ResY是渲染分辨率不要偷懶省略。缺省情況下UE會(huì)嘗試讀取桌面分辨率而在無(wú)頭服務(wù)器上桌面分辨率根本就不存在結(jié)果就是分母為0畫(huà)面起不來(lái)。4.2 無(wú)窗口模式下的分辨率、日志和進(jìn)程管理分辨率這個(gè)坑我多說(shuō)一句。像素流送的實(shí)際輸出分辨率不一定等于-ResX/-ResY設(shè)定的值瀏覽器端還可以在URL參數(shù)里指定分辨率比如?Resolution1280x720。但服務(wù)器端默認(rèn)分辨率必須存在否則編碼器初始化都會(huì)出問(wèn)題。建議直接設(shè)成你和用戶約定的標(biāo)準(zhǔn)分辨率比如1920x1080。進(jìn)程管理方面直接用./MyProject在前臺(tái)跑SSH一斷進(jìn)程就沒(méi)了。生產(chǎn)環(huán)境建議用systemd或者screen/tmux拉起。我的習(xí)慣是寫(xiě)一個(gè)啟動(dòng)腳本里面把環(huán)境變量、xvfb參數(shù)、啟動(dòng)參數(shù)都放進(jìn)去然后用systemd管理。日志排查是重頭戲。加-log參數(shù)后UE會(huì)把日志輸出到標(biāo)準(zhǔn)輸出同時(shí)也會(huì)寫(xiě)到Saved/Logs/項(xiàng)目名.log。用下面的命令可以實(shí)時(shí)跟蹤像素流送相關(guān)的日志tail -f Saved/Logs/MyProject.log | grep -i pixel4.3 從日志快速判斷是否跑通啟動(dòng)后瀏覽器訪問(wèn)http://服務(wù)器IP:端口點(diǎn)擊Play。整個(gè)過(guò)程能不能成日志里都有信號(hào)。成功時(shí)UE日志里會(huì)出現(xiàn)類(lèi)似這樣的關(guān)鍵行Pixel Streaming streamer started—— 說(shuō)明像素流送插件正常啟動(dòng)Pixel Streaming Player connected—— 說(shuō)明有瀏覽器連上來(lái)了Pixel Streaming ICE connection established—— 說(shuō)明WebRTC通道建立成功。如果一直卡在連接中日志里可能會(huì)出現(xiàn)Failed to connect to signalling server—— UE連不上信令服務(wù)器重點(diǎn)查IP、端口和防火墻No active video stream—— 編碼器沒(méi)輸出重點(diǎn)查GPU驅(qū)動(dòng)和編碼器狀態(tài)ICE failed—— 網(wǎng)絡(luò)協(xié)商失敗重點(diǎn)查UDP端口和STUN配置。只要看到Player connected和ICE connection established兩條畫(huà)面基本就出來(lái)了。如果瀏覽器里還是黑的優(yōu)先檢查顯卡驅(qū)動(dòng)和編碼器下一節(jié)詳細(xì)說(shuō)。5. 并發(fā)、編碼器與性能Linux下的調(diào)優(yōu)記錄畫(huà)面跑通只是第一步。真正上生產(chǎn)之前你得回答一個(gè)問(wèn)題一臺(tái)服務(wù)器能撐多少人同時(shí)在線這跟編碼器有直接關(guān)系。5.1 硬編和軟編怎么選NVENC和CPU的取舍像素流送在Linux平臺(tái)上的編碼路徑主要有硬件編碼NVENC和軟件編碼x264等兩種。默認(rèn)情況下如果檢測(cè)到NVIDIA GPUUE會(huì)優(yōu)先使用NVENC硬編。硬編的好處是CPU占用極低同一臺(tái)機(jī)器可以多跑幾個(gè)UE實(shí)例。軟編的好處是兼容性更好不依賴(lài)NVIDIA驅(qū)動(dòng)版本但CPU占用極高——我用CPU軟編跑一個(gè)1080p畫(huà)面占用能到80%以上稍微復(fù)雜點(diǎn)的場(chǎng)景直接掉幀。所以如果你用的是NVIDIA GPU系列服務(wù)器第一件事就是把顯卡驅(qū)動(dòng)和CUDA裝對(duì)。像素流送的硬編依賴(lài)NVIDIA Video Codec SDK而它和驅(qū)動(dòng)的版本是綁定的。我遇到過(guò)一次驅(qū)動(dòng)版本太老導(dǎo)致NVENC初始化失敗UE自動(dòng)回退到軟編結(jié)果CPU被打滿、延遲飆升最后在日志里看到一句Pixel Streaming encoder failed to initialize才反應(yīng)過(guò)來(lái)。檢查驅(qū)動(dòng)和硬編是否就緒可以在服務(wù)器上跑nvidia-smi如果nvidia-smi能看到GPU下一步檢查驅(qū)動(dòng)支持的編碼器屬性確認(rèn)編碼器沒(méi)被占用。還有一個(gè)容易漏的地方如果服務(wù)器用了虛擬化GPUvGPU或者顯卡透?jìng)鞣桨窷VENC經(jīng)常不工作表現(xiàn)為nvidia-smi正常但像素流送日志報(bào)編碼器初始化失敗。要避免這種情況盡量選物理GPU直通別在云主機(jī)里搞GPU虛擬化。5.2 并發(fā)上限、顯存與延遲的實(shí)測(cè)參考并發(fā)這塊我實(shí)測(cè)下來(lái)的經(jīng)驗(yàn)是一張主流GPU開(kāi)一個(gè)UE實(shí)例一路串流60幀基本沒(méi)問(wèn)題開(kāi)多個(gè)UE實(shí)例每個(gè)實(shí)例一路串流GPU顯存和編碼器帶寬會(huì)成為瓶頸。每個(gè)實(shí)例默認(rèn)會(huì)占用一部分顯存用于渲染緩沖具體量取決于分辨率和場(chǎng)景復(fù)雜度。像素流送的編碼器默認(rèn)會(huì)把幀率、碼率做動(dòng)態(tài)調(diào)整。延遲方面局域網(wǎng)環(huán)境一般能做到50到100毫秒公網(wǎng)環(huán)境受網(wǎng)絡(luò)波動(dòng)影響通常會(huì)在100到200毫秒之間。如果你的項(xiàng)目對(duì)操作手感要求高比如競(jìng)速類(lèi)游戲這個(gè)延遲體感還是明顯的建議盡量讓服務(wù)器和用戶在同城或同區(qū)域不要跨大洲部署。并發(fā)策略資源消耗適用場(chǎng)景單實(shí)例多路流低但單實(shí)例故障影響面大內(nèi)部演示、低并發(fā)多實(shí)例每實(shí)例一路資源翻倍但隔離性好公網(wǎng)并發(fā)用戶容器自動(dòng)擴(kuò)縮容初始成本高長(zhǎng)期最省正式上線、動(dòng)態(tài)流量5.3 幀率和編碼參數(shù)調(diào)整像素流送支持通過(guò)前端URL參數(shù)或服務(wù)器端配置調(diào)整碼率和幀率。比如在URL后面加?MaxBitrate20000000可以把最大碼率調(diào)到20Mbps畫(huà)質(zhì)會(huì)明顯提升但對(duì)帶寬要求也高。實(shí)際調(diào)優(yōu)時(shí)我的建議是先固定分辨率再調(diào)碼率最后調(diào)幀率。分辨率和碼率決定了畫(huà)面清晰度幀率決定了流暢度。對(duì)于展廳類(lèi)應(yīng)用1080p30幀、碼率8到12Mbps是我的常用起點(diǎn)對(duì)于游戲操作類(lèi)場(chǎng)景建議60幀但碼率要相應(yīng)提高否則畫(huà)面會(huì)糊。6. 部署中容易忽略的細(xì)節(jié)音頻、Pointer Lock、STUN與網(wǎng)絡(luò)穿透這三塊內(nèi)容不是“一定要配”但一旦不管用戶實(shí)際體驗(yàn)就會(huì)出問(wèn)題。6.1 音頻回傳從無(wú)聲到有聲音的排查像素流送會(huì)把服務(wù)器端的系統(tǒng)音頻捕獲后傳給瀏覽器。如果項(xiàng)目里有背景音樂(lè)、場(chǎng)景音效服務(wù)器的音頻是必要的。問(wèn)題在于無(wú)桌面服務(wù)器環(huán)境通常沒(méi)有音頻設(shè)備。UE啟動(dòng)時(shí)會(huì)掃描ALSA/PulseAudio設(shè)備如果找不到日志里會(huì)出現(xiàn)類(lèi)似No audio input device found的提示這時(shí)候串流畫(huà)面正常但完全沒(méi)聲音。我的處理辦法是加載ALSA loopback模塊讓系統(tǒng)有一個(gè)虛擬音頻設(shè)備sudo modprobe snd-aloop然后啟動(dòng)UE時(shí)加參數(shù)-AudioInputDevice指定設(shè)備。設(shè)備名可以通過(guò)arecord -l或pactl list sources short查看。裝好PulseAudio并啟動(dòng)服務(wù)后UE通常能自動(dòng)識(shí)別到設(shè)備。如果你的服務(wù)器確實(shí)沒(méi)有音頻需求也可以直接忽略這個(gè)問(wèn)題但不建議在正式交付時(shí)給用戶一個(gè)無(wú)聲版本。6.2 瀏覽器Pointer Lock鼠標(biāo)鍵盤(pán)不聽(tīng)使喚的根源像素流送默認(rèn)會(huì)把鼠標(biāo)鍵盤(pán)事件通過(guò)WebRTC通道回傳給UE進(jìn)程。但在瀏覽器里鼠標(biāo)要被“捕獲”住必須觸發(fā)Pointer Lock API。這個(gè)API的兩個(gè)限制經(jīng)常被忽略其一如果頁(yè)面被嵌在iframe里而iframe標(biāo)簽沒(méi)有allowpointer-lock屬性瀏覽器會(huì)靜默拒絕鼠標(biāo)鎖定請(qǐng)求。表現(xiàn)為用戶點(diǎn)擊畫(huà)面后第一次能控制視角一旦鼠標(biāo)移出畫(huà)面就再也抓不回來(lái)了。其二Pointer Lock需要用戶手勢(shì)觸發(fā)。如果前端代碼不是在用戶點(diǎn)擊Play的回調(diào)里獲取鼠標(biāo)鎖而是在頁(yè)面加載時(shí)自動(dòng)獲取會(huì)被瀏覽器直接攔截。排查方法很簡(jiǎn)單點(diǎn)擊畫(huà)面按F12看Console有沒(méi)有關(guān)于Pointer Lock的報(bào)錯(cuò)。如果有要么給iframe加權(quán)限要么調(diào)整前端代碼的觸發(fā)時(shí)機(jī)。6.3 STUN/TURN跨網(wǎng)絡(luò)訪問(wèn)連不上的隱藏原因這是最容易讓人崩潰的坑。場(chǎng)景是這樣的我在云服務(wù)器上部署好了像素流送本機(jī)測(cè)試完全正常但換個(gè)網(wǎng)絡(luò)比如手機(jī)熱點(diǎn)訪問(wèn)頁(yè)面能加載點(diǎn)擊Play后永遠(yuǎn)處于“connecting”狀態(tài)。根因是WebRTC的P2P特性。瀏覽器和UE進(jìn)程需要通過(guò)網(wǎng)絡(luò)協(xié)商出一條可用的數(shù)據(jù)通道跨網(wǎng)絡(luò)尤其是雙方都在NAT后面時(shí)默認(rèn)的P2P協(xié)商大概率失敗。解決方法是配置STUN服務(wù)器讓雙方發(fā)現(xiàn)自己的公網(wǎng)地址如果網(wǎng)絡(luò)環(huán)境特別嚴(yán)格還需要TURN服務(wù)器做中繼轉(zhuǎn)發(fā)。信令服務(wù)器的配置文件里通常有iceServers字段加上STUN地址{ iceServers: [ { urls: stun:your-stun-server:3478 } ] }STUN服務(wù)器可以自己搭用coturn是常見(jiàn)方案sudo apt install coturn turnserver -a -f -n --stun-only --listening-port3478如果你的用戶群體都集中在公司內(nèi)網(wǎng)這個(gè)配置可以跳過(guò)。但如果要做公網(wǎng)訪問(wèn)STUN/TURN就是必須項(xiàng)不是可選項(xiàng)。7. 容器化與生產(chǎn)環(huán)境把這套流程固定成基礎(chǔ)設(shè)施手動(dòng)敲命令啟動(dòng)UE實(shí)例只能撐住演示階段。一旦要正式上線容器化是繞不開(kāi)的。7.1 Docker鏡像從系統(tǒng)依賴(lài)到UE產(chǎn)物的打包Linux像素流送容器化的關(guān)鍵在于基礎(chǔ)鏡像要匹配UE編譯環(huán)境。如果你的UE版本是在Ubuntu 20.04上打包的那容器基礎(chǔ)鏡像最好也用Ubuntu 20.04否則glibc版本不匹配會(huì)直接跑不起來(lái)。一個(gè)能跑的最小Dockerfile骨架是這樣FROM nvidia/cuda:12.2.0-base-ubuntu20.04 RUN apt-get update apt-get install -y \ libnss3 libxcomposite1 libxcursor1 libxi6 \ libasound2 libpulse0 xvfb curl WORKDIR /app COPY MyProjectLinux /app/MyProjectLinux EXPOSE 8888 CMD [xvfb-run, -a, ./MyProjectLinux, -RenderOffScreen, \ -PixelStreamingIP127.0.0.1, -PixelStreamingPort8888, \ -ResX1920, -ResY1080]這里有幾個(gè)注意點(diǎn)基礎(chǔ)鏡像要帶xvfb否則UE在容器里沒(méi)有顯示環(huán)境用nvidia/cuda鏡像而不是普通Ubuntu鏡像可以省去在容器里再裝NVIDIA驅(qū)動(dòng)的環(huán)節(jié)啟動(dòng)命令里的-PixelStreamingIP要指向信令服務(wù)器可達(dá)的地址如果信令服務(wù)器也在容器里建議直接用Docker的network模式別寫(xiě)死IP。7.2 顯存透?jìng)骱唾Y源調(diào)度UE像素流送必須訪問(wèn)GPU所以普通Docker跑不起來(lái)需要NVIDIA Container Toolkit來(lái)把宿主機(jī)的GPU透?jìng)鹘o容器。安裝好后啟動(dòng)命令是docker run --gpus all --network host -d --name pixel-stream my-pixel-image這里用--network host可以避免端口映射和STUN穿透的問(wèn)題信令服務(wù)器和UE進(jìn)程直接共享宿主網(wǎng)絡(luò)棧延遲也更低。缺點(diǎn)是端口管理要靠系統(tǒng)層面去做每個(gè)UE實(shí)例監(jiān)聽(tīng)不同的信令端口。如果你打算跑多個(gè)UE實(shí)例并動(dòng)態(tài)擴(kuò)縮容建議給每個(gè)實(shí)例分配獨(dú)立的信令端口比如8888、8889、8890再在前端做一個(gè)會(huì)話調(diào)度把用戶分配到空閑實(shí)例上。這個(gè)調(diào)度環(huán)節(jié)可以自己做也可以參考云渲染平臺(tái)的思路但核心思想都是一樣的一個(gè)UE實(shí)例對(duì)應(yīng)一路串流實(shí)例池按需擴(kuò)容。7.3 服務(wù)編排一條命令拉起整套環(huán)境用Docker Compose可以同時(shí)管理信令服務(wù)器和若干個(gè)UE實(shí)例示例version: 3 services: signalling: image: node:18 command: node /app/SignallingWebServer/server.js volumes: - ./SignallingWebServer:/app network_mode: host streamer-1: image: my-pixel-image network_mode: host environment: - PIXEL_STREAMING_PORT8888真正上線前我建議把“健康檢查”加上定期檢查UE進(jìn)程的WebSocket端口是否響應(yīng)如果一個(gè)實(shí)例崩潰了自動(dòng)從調(diào)度池里摘掉并拉起一個(gè)新的容器。這一套做完才算真正把Linux像素流送部署成了可運(yùn)維的基礎(chǔ)設(shè)施。我在實(shí)際項(xiàng)目里的體會(huì)是Linux上跑像素流送最大的敵人不是技術(shù)本身而是“不知道往哪查問(wèn)題”。只要把信令握手、編碼器、網(wǎng)絡(luò)協(xié)商這三條線的日志和排查鏈路建立起來(lái)絕大多數(shù)問(wèn)題都能在十分鐘內(nèi)定位。如果你正準(zhǔn)備上線建議先從單實(shí)例跑通開(kāi)始再逐步加容器化和自動(dòng)擴(kuò)縮容不要一上來(lái)就搞復(fù)雜架構(gòu)。這套流程比我第一次部署時(shí)少了至少一半的試錯(cuò)成本照著做應(yīng)該能幫你省下不少時(shí)間。