TinyPilot: Build a KVM Over IP for Under $100

Michael Lynch

TinyPilot:不到 100 美元自製 KVM Over IP

原文由 Michael Lynch 發布,訂閱此部落格

TinyPilot 是我打造的平價、開源遠端電腦控制裝置。即使在作業系統啟動前也能運作,所以我都用它來在裸機自宅實驗室伺服器上安裝新作業系統、除錯開機失敗的問題。

這篇文章會詳細分享我打造 TinyPilot 的經驗,並示範如何用 Raspberry Pi 花不到 100 美元自製一台。

透過 TinyPilot 連接兩台電腦的照片

透過 Microsoft Surface 上的 Chrome,使用 TinyPilot 控制我的 Ubuntu 筆電

我不想聽你的心路歷程,直接告訴我怎麼組就好

如果你跟掃興鬼一樣,沒興趣聽我開發 TinyPilot 過程中精彩的成敗血淚史,請直接跳到「如何自製你的 TinyPilot」一節。

展示

為什麼需要 TinyPilot?

幾年前,我為了測試軟體自己組了一台家用伺服器。這是一筆很划算的投資,我每天都在用。

我的自宅實驗室 VM 伺服器照片

我在 2017 年打造的自宅實驗室伺服器,用來運行虛擬機器

這台伺服器沒有接鍵盤或螢幕,因為我都是透過 ssh 或網頁介面存取。這樣的設定很方便,但小問題也會變成大麻煩。

每隔幾個月,我總會搞砸什麼,導致伺服器無法開機或連不上網路,等於把自己鎖在門外。為了讓它重新運作,我得把所有線都拔掉,把伺服器搬到桌邊,手忙腳亂地把鍵盤、螢幕的線接上來。

市售解決方案

朋友們對 iDRAC 的使用經驗讚不絕口。這是 Dell 伺服器上的一塊晶片,從系統一開機就能提供虛擬主控台。我曾短暫考慮在下一台家用伺服器上用 iDRAC,但看到高昂的價格就馬上打退堂鼓。光是授權就要 300 美元,還得搭配昂貴的專用硬體。

iDRAC 9 Enterprise 授權要價 300 美元的截圖

Dell 的 iDRAC 技術每台機器授權費就要 300 美元,還不含硬體成本

接著,我研究了市售的 KVM over IP 解決方案。它們的功能跟 Dell 的 iDRAC 類似,但屬於外接裝置,接到電腦的鍵盤、影像和滑鼠連接埠(KVM 之名由此而來)。可惜它們更貴,每台要價 500 到 1000 美元。

Raritan Dominion KVM over IP 購買頁面截圖

市售 KVM over IP 裝置的價格介於 500 到 1,000 美元之間。

雖然我很懶得搬伺服器,但為了每年省下幾次插拔線的麻煩就花 500 美元,實在說不過去。

於是,我做了任何夠不理性的程式設計師都會做的事:花上好幾百個小時自己打造一台 KVM over IP。

用 Raspberry Pi 打造 KVM over IP

Raspberry Pi 是一款小巧、便宜的單板電腦。效能足以運行完整的桌機作業系統,價格卻只要 30 到 60 美元,因此深受自造者和程式設計師喜愛。

手掌上的 Raspberry Pi

Raspberry Pi 是一台功能完整的電腦,濃縮在一塊單板上,價格只要 30 到 60 美元。

近期版本的 Pi 支援 USB On-The-Go(USB OTG),能讓 Pi 模擬成鍵盤、隨身碟、麥克風等 USB 裝置。

為了驗證「用 Pi 當 KVM」的想法是否可行,我做了一個名為 Key Mime Pi 的簡易網頁應用程式作為概念驗證。

Key Mime Pi 網頁介面截圖

Key Mime Pi,我打造的 TinyPilot 早期雛形,只支援鍵盤轉送功能。

Key Mime Pi 透過 USB 連接到另一台電腦,並註冊為 USB 鍵盤。它同時提供一個網頁,監聽 JavaScript 的按鍵事件。當使用者輸入時,Key Mime Pi 會捕捉按鍵事件,再透過虛擬的 USB 鍵盤轉成按鍵訊號,讓按鍵出現在目標電腦上。我在前一篇文章中詳細說明過這個運作原理。

擷取影像的挑戰

如果看不到螢幕畫面,光有鍵盤轉送也沒什麼用。下一步很明顯,就是想辦法讓 Pi 擷取伺服器的顯示輸出,並在瀏覽器中呈現。

我第一次嘗試擷取影像是用 Lenkeng LKV373A HDMI 延長器。Daniel Kučera(也就是 danman)對這個裝置做了非常出色的逆向工程。當時在 eBay 上向中國賣家購買大約只要 40 美元,看起來是最好的選擇。

Lenkeng LKV373A HDMI 延長器照片

Lenkeng LKV373A HDMI 延長器是我第一次嘗試用來擷取 HDMI 影像的裝置。

擷取影像很棘手,因為 LKV373A 發射器本來就不是影像擷取裝置。它的設計用途是與 LKV373A 接收器配對,將網路串流轉回 HDMI 輸出。在 danman 的研究中,他發現了一種攔截並擷取影像串流的方法,但 LKV373A 使用的是非標準的 RTP 協定變體,很少有影像工具能理解。

幸好,danman 向 ffmpeg 提交了一個修補程式來處理 LKV373A 這種古怪的行為,所以我才能用 ffmpeg 的播放器來呈現串流:

ffplay -i udp://239.255.42.42:5004
用 ffplay 呈現來自 LKV373A 的影像串流截圖

用 ffplay 呈現來自 LKV373A 的影像串流

就在這裡,我第一次嚐到困擾整個專案的問題:延遲。目標電腦與我桌機上的影像播放之間,幾乎有一秒鐘的延遲。

Lenkeng LKV373A HDMI 延長器照片

在任何重新編碼之前,LKV373A 就已經帶來了 838 毫秒的延遲。

我試著調整 ffplay 的各種命令列參數來加速串流,但始終無法壓到 800 毫秒以下。而且這還是在配備高階 GPU 和 CPU 的桌機上。對於我那台小小的 Raspberry Pi 來說,效能前景實在不樂觀。

幸好,我完全是靠巧合找到了更好的解決方案。

HDMI 轉 USB 轉接器

有次我在 Twitter 上漫無目的地滑動時,剛好看到了來自 Arsenio Dev 的一則推文,提到他剛買了一個平價的 HDMI 轉 USB 轉接器:

Rufus 截圖

來自 Arsenio Dev 的推文讓我發現了更好的影像擷取方案。

能以 1080p 解析度、每秒 30 幀擷取影像,聽起來好得有點不真實,所以我還是從 eBay 下單了一個。含運費只要 11 美元。我甚至不知道該怎麼稱呼它——它沒有品牌名稱,所以我就叫它「HDMI 轉接器」就好。市面上有好幾種外觀,但其實都是同一顆 MacroSilicon MS2109 晶片換了不同的外殼而已。

eBay 上售價 11.20 美元的 HDMI 轉接器截圖

eBay 上售價 11.20 美元、免運費的 HDMI 轉 USB 轉接器

幾天後裝置到貨,效果讓我驚艷。完全不用任何調整,只要插上 Raspberry Pi,就會被辨識為 UVC 影像擷取裝置。

$ sudo v4l2-ctl --list-devices
bcm2835-codec-decode (platform:bcm2835-codec):
        /dev/video10
        /dev/video11
        /dev/video12

UVC Camera (534d:2109): USB Vid (usb-0000:01:00.0-1.4): <<< HDMI capture dongle
        /dev/video0
        /dev/video1

幾分鐘內,我就能擷取並重新轉送 HDMI 影像了:

# On the Pi
ffmpeg \
  -re \
  -f v4l2 \
  -i /dev/video0 \
  -vcodec libx264 \
  -f mpegts udp://10.0.0.100:1234/stream

# On my Windows desktop
ffplay.exe -i udp://@10.0.0.100:1234/stream

而且它超級方便。LKV373A 幾乎有磚頭那麼大,還需要獨立的電源和網路線。HDMI 轉接器只有隨身碟大小,只需要一個 USB 埠就夠了。

Lenkeng LKV373A 與 HDMI 轉接器的比較

Lenkeng LKV373A HDMI 延長器(左)體積較大、需要的連接也比 HDMI 轉接器(右)多。

唯一的問題,還是延遲。Pi 重新廣播的影像串流比來源電腦慢了 7 到 10 秒。

Lenkeng LKV373A 與 HDMI 轉接器的比較

使用 ffmpeg 從我的 Pi 串流影像時,影片延遲高達 10 秒。

我不確定這個延遲是來自轉接器本身、Pi 上的 ffmpeg,還是桌機上的 ffplay。Arsenio Dev 回報的延遲只有 20 毫秒,所以看起來只要深入研究 ffmpeg 那些艱澀又神祕的命令列參數,應該有機會達到更快的效能。

又一次幸運讓我免於這個痛苦的任務。

借鏡類似的專案

當我發表關於 Key Mime Pi 的前一篇部落格文章後,收到了 Max Devaev 的留言,他建議我去看看他的專案 Pi-KVM

Max 的留言:Hi:) Take a look at this project: https://github.com/pikvm/pikvm We have already done and debugged many things

Max Devaev 向我推薦了他已有的 Pi-KVM 專案。

GPIO 接腳

我之前使用麵包板的經驗,就是不小心把它們燒熔了

我曾稍微看過 Pi-KVM,但它需要麵包板和焊接的要求把我嚇退了。

在 Max 的建議下,我又仔細研究了一次 Pi-KVM,特別想了解他如何解決影像延遲問題。我注意到他是透過一個叫做 uStreamer 的工具來擷取影像。

:與 Max 進一步討論後,我得知 Pi-KVM 其實也支援不需要焊接或麵包板的組裝方式。

uStreamer:超快速的影像串流工具

你有沒有遇過一種工具,好用到連你還沒想到的問題都幫你解決了?

uStreamer 一裝好就把我的延遲從 8 秒降到 500 到 600 毫秒。但它還省去了我一整串額外的工作。

使用 uStreamer 和 HDMI 轉接器時 500 毫秒的延遲

uStreamer 讓我的延遲降低了 15 倍。

在使用 uStreamer 之前,我還不確定該如何把 ffmpeg 的影像送進使用者的瀏覽器,但我知道應該有辦法做到。我試過這個大致正確的教學,用 HLS 把影像從 ffmpeg 透過管線送到 nginx,但它反而增加了更多延遲。而且還留下不少未解的問題,例如如何在 HDMI 線連接或拔除時啟動與停止串流,以及如何把影像轉成瀏覽器友善的格式。

uStreamer 一次解決了所有這些問題。它自帶一個極簡的 HTTP 伺服器,以Motion JPEG 格式提供影像,這是瀏覽器原生就能播放的格式。我再也不用煩惱 HLS 串流,或是讓 ffmpeg 和 nginx 彼此溝通。

這個工具功能如此完整,我還以為 Max 只是從某個更成熟的專案 fork 來的,結果我錯了。這個瘋狂的傢伙為了榨出 Pi 硬體的最大效能,用 C 語言自己寫了一個影像編碼器。我馬上捐款給 Max,也邀請所有使用他軟體的人一起支持。

進一步改善影像延遲

uStreamer 把我的延遲從 10 秒降到約 600 毫秒。這已經是一大躍進,但延遲感還是很明顯。我跟 Max 說,如果他能找到提升效能的方法,我很樂意進一步資助 uStreamer,於是我們就聊了起來。

Max 對我用的 HDMI 轉接器很感興趣,因為他從沒見過這個型號。他邀請我用 tmate 建立一個共享的 shell 連線,讓他能遠端存取我的 Pi。

Max 提出透過 tmate 幫我忙的對話截圖

Max 說他要嘛幫我改善延遲,要嘛栽贓讓我犯下聯邦重罪。幸好,他最後做的是前者。

在我的硬體上測試 uStreamer 幾分鐘後,Max 執行了 v4l2-ctl 工具,看到一行讓他眼睛一亮、但我完全看不懂的資訊:

$ sudo v4l2-ctl --all
Driver Info:
        Driver name      : uvcvideo
        Card type        : UVC Camera (534d:2109): USB Vid
...
Format Video Capture:
        Width/Height      : 1280/720
        Pixel Format      : 'MJPG' (Motion-JPEG)
...
Streaming Parameters Video Capture:
        Capabilities     : timeperframe
        Frames per second: 30.000 (30/1)

HDMI 轉接器本身就是以 Motion JPEG 格式傳送影像串流!uStreamer 的硬體加速編碼雖然很快,但其實完全是多餘的,因為現代瀏覽器本來就能原生播放 Motion JPEG。

我們把 uStreamer 設定為跳過重新編碼,直接原樣轉送影像串流。

在 Pi 上省去重新編碼步驟後,延遲僅剩 200 毫秒的照片

在 Pi 上跳過額外的重新編碼步驟,讓延遲從 600 毫秒降到 200 毫秒。

延遲就這樣從 600 毫秒一路降到 200 毫秒。雖然不是零延遲,但已經低到用個幾分鐘就會忘記它的存在。

TinyPilot 實際運作

還記得這篇文章一開頭我說,想要 TinyPilot 是為了在無頭的 VM 伺服器開機前就能存取它嗎?現在它真的做到了,而且我也真的這樣在用!

我在 Key Mime Pi 的基礎上改良,打造了一個整合影像擷取功能的新網頁介面:

今年我組了一台新的無頭 VM 伺服器,並用 TinyPilot 來安裝 Proxmox,這是一套開源的 Hypervisor 以及管理虛擬機器的網頁介面。

TinyPilot 讓我能直接在瀏覽器中完成整個安裝過程。這絕對比以前搬電腦、換線材的舊方法愉快多了。

如何自製你的 TinyPilot

零件清單

安裝 Raspberry Pi OS Lite

首先,在 microSD 卡上安裝 Raspberry Pi OS Lite(舊稱 Raspbian)。

Rufus 截圖

我用 Rufus 來燒錄 Pi 的 microSD 卡,不過任何整碟映像工具都可以用。

在 microSD 卡的開機磁區中放入一個名為 ssh 的檔案,即可啟用 SSH 存取。如果你要透過無線網路連線,還需要一個 wpa_supplicant.conf 檔案

準備好 microSD 卡後,將它插入 Pi 裝置中。

安裝外殼(選配)

Raspberry Pi 4 發熱量很大是出了名的。沒有散熱也能跑,但時間一久很可能會遇到穩定性問題。

我喜歡這款極簡外殼,因為它價格便宜,又能以被動方式為 Pi 散熱,不需要額外供電風扇的複雜設計:

Raspberry Pi 極簡鋁製外殼

這款極簡鋁製外殼能在不使用風扇的情況下,有效為 Pi 散熱。

透過 USB 連接目標機器

要讓 TinyPilot 能作為虛擬鍵盤運作,請將 Pi 的 USB-C 埠接到目標機器的 USB-A 埠:

連接至 Raspberry Pi 的 USB 連接線連接至目標電腦的 USB 連接線

使用 USB-C 對 USB-A 傳輸線,將 USB-C 端接到 Pi 的 USB-C 埠,USB-A 端接到目標電腦。

:請優先使用 USB 3.0 埠,因為它們能為 Pi 提供更多電力。

接上 HDMI 擷取轉接器

要完成硬體組裝,請將 HDMI 轉接器插入 Pi 的其中一個 USB 埠。接著將 HDMI 線接到轉接器上,另一端插入目標電腦的顯示輸出埠。

連接至 Raspberry Pi 的 HDMI 輸入來自目標電腦的 HDMI 輸出連接

將目標電腦的顯示輸出接到 HDMI 轉接器上,再將轉接器插入 Pi 的 USB 埠。

:如果要連接的電腦沒有 HDMI 輸出,應該也可以使用 DisplayPort 轉 HDMI 線DVI 轉 HDMI 線,不過我個人沒有實際測試過。

接上網路線

如果你要透過有線網路連接 Pi,請將網路線接到 Pi 的乙太網路埠:

已接上 Pi 裝置的乙太網路線照片

將網路線接到你的 Pi 上。

:如果你已經依照前述步驟加入 wpa_supplicant.conf 檔案設定好無線網路,就可以跳過這個步驟。

安裝 TinyPilot 軟體

透過 SSH 連進你的 Pi 裝置(Raspberry Pi OS 的預設帳號密碼為 pi / raspberry),然後執行以下指令:

curl -sS https://raw.githubusercontent.com/tiny-pilot/tinypilot/master/quick-install \
  | bash -
sudo reboot

如果你對把網路上隨便一個腳本導向 shell 執行抱持合理的懷疑,我鼓勵你先檢查原始碼

這個腳本會透過我的 TinyPilot Ansible role 建立一個自給自足的 Ansible 環境。它會安裝四個每次開機都會執行的服務:

  • nginx:熱門的開源網頁伺服器
  • ustreamer:輕量級的 HTTP 影像串流伺服器
  • usb-gadget:用來啟用 Pi「USB gadget 模式」的腳本,讓 Pi 能模擬成 USB 裝置
  • tinypilot:我為 TinyPilot 打造的網頁介面

使用 TinyPilot

執行安裝腳本後,你可以在以下位址存取 TinyPilot:

TinyPilot 網頁介面截圖

設定完成後,你可以在區域網路上透過 http://raspberrypi/ 存取 TinyPilot 的網頁介面。

供電問題

這個架構最大的限制是供電。依賴目標電腦供電,意味著當目標關機時,Pi 就會遭遇非預期的斷電。

此外,Pi 4 需要 3 安培才能穩定運作,雖然在較低功率下也能跑。電腦的 USB 3.0 埠只能提供 0.9 安培,USB 2.0 更只有 0.5 安培,這就是為什麼你可能會在 Pi 的系統日誌中看到這些警告:

 $ sudo journalctl -xe | grep "Under-voltage"
Jun 28 06:23:15 tinypilot kernel: Under-voltage detected! (0x00050005)

為了解決這個問題,我與一家工程公司合作,打造了一塊客製化電路板,將 Pi 的 USB-C 埠一分為二。第一個埠接收 USB 電源,因此仍可為 Pi 提供完整的 3 安培電力。第二個埠負責 USB 資料輸出,讓 Pi 仍能模擬成 USB 鍵盤。

供電分接器特寫已接上 Raspberry Pi 和 microUSB 傳輸線的供電分接器

TinyPilot Power Connector 讓 Pi 能透過 USB-C 埠接收 3 安培電力,同時不失去 USB OTG 功能。

重要的是,供電分接器的資料埠排除了 USB 電源線。這能確保電腦電源與 Pi 電源之間的電壓差不會造成不必要的電力回流。

:若沒有使用適當的分接器,在 Pi 連接著電腦的同時又從外部電源供電,可能有損壞硬體的風險。更多詳細資訊請參閱 TinyPilot wiki

原始碼

TinyPilot 的軟體以寬鬆的 MIT 授權開源:

  • tinypilot:TinyPilot 的網頁介面與後端程式。
  • ansible-role-tinypilot:用於將 TinyPilot 及其相依套件作為 systemd 服務安裝的 Ansible role。

現成的 TinyPilot 裝置

現成的 TinyPilot 裝置可在 TinyPilot 官網購買。我是 TinyPilot 這家公司的原始創辦人,但我在 2024 年 4 月已經將公司出售,現在除了是熱情的使用者外,與該公司已無任何關係。


特別感謝 Max Devaev 在 uStreamer 上的傑出貢獻,以及他對 TinyPilot 的付出。

本文章由 muse-spark-1.2-contributor 進行翻譯

留言