$100 以内构建一台 KVM over IP 设备

Note: KVM over IP 指通过网络远程控制键盘、视频和鼠标
TinyPilot 是一款我自制的远程控制设备,它开源并且构建的价格也很合适。它可以在操作系统启动之前就工作,所以你可以使用它为计算机安装操作系统,或者像我一样为我的 无头服务器 调试启动失败的问题。

这篇帖子是我创建 TinyPilot 的过程,并且为你展示我如何使用 树莓派 在 $100 以下构建这台设备。

使用 TinyPilot 连接两台电脑的照片

在 Surface 上通过 TinyPilot 控制 Ubuntu 笔记本电脑

我不想听你的故事,我只是想知道如何构建这个设备 🔗︎

如果你只是被这个设备所吸引,不关心我开发过程中遇到的精彩和挫折,可以跳转到这个部分 如何去构建你自己的 TinyPilot

开发 TinyPilot 的背景 🔗︎

几年之前,为了测试软件,我组装了我自己的家庭服务器(Home Server)。这是非常有价值的投资,直到今天我还在使用它。

我的 homelab VM server 照片

我于 2017 年搭建的用于托管虚拟机的 homelab

我使用 SSH 或者 Web 界面去访问这台服务器,所以我没有给它配备显示器和键盘。这是一个方便的配置,但这也常常给我带来很多麻烦。

有时因为我的一些操作失误(频率在几个月一次),会导致服务器无法启动、无法加入网络,导致我无法访问它。为了恢复运行,我不得不拔掉所有连接线,将服务器拖到我的桌子旁边,然后连接键盘、显示器以及各种线材到服务器。

商业解决方案 🔗︎

朋友们给我安利了 iDRAC,对它的使用体验赞不绝口。iDRAC 是戴尔服务器中的一种芯片,可以在系统启动时提供虚拟控制台。我短暂地考虑过为我的下一台服务器添加 iDRAC,但是它昂贵的价格让我很快打消了这个想法。单单 License 就需要 $300,这还不包含昂贵的定制硬件。

iDRAC 9 Enterprise 许可证价格为 300 美元的截图

戴尔 iDRAC 技术的许可证费用为每台机器 300 美元,不包括硬件成本

随后我考察了商用的 KVM over IP 解决方案。这类设备功能与戴尔 iDRAC 类似,但属于外接装置,需要连接电脑的键盘(Keyboard)、显示器(Video)和鼠标(Mouse)端口(KVM 即由此得名)。遗憾的是其价格更为高昂,单台设备售价在 500 至 1000 美元之间。

Raritan Dominion KVM over IP 购买页面截图

商用的 KVM over IP 设备价格在 $500~$1000

虽然我不愿意来回来去地搬服务器,但是为了节省插拔线缆的功夫而花 $500,实在有点说不过去,更何况一年只有几次。

于是,我做了每个不够理性的程序员都会做的事:花了几百个小时打造自己的 KVM over IP 设备。

使用树莓派构建 KVM over IP 设备 🔗︎

树莓派 是小型、廉价的 单板机。它的性能足以运行完整的桌面操作系统,并且售价只需要 $30-60,所以它在开发者和业余爱好者群体中很受欢迎。

手掌中的树莓派

树莓派是一款功能齐全的计算机,所有核心部件都集成在一块小型主板上,价格仅为 $30~60

最新版本的树莓派(作者指的是 树莓派 4B)支持 USB on-the-go(USB OTG),这允许树莓派模拟 USB 设备,例如键盘、U 盘、麦克风。

为了验证 Pi-as-KVM 这个想法,我创建了一个简单的 Web 应用:Key Mime Pi

Key Mime Pi网页界面截图

Key Mime Pi,这是我早期开发的TinyPilot前身,当时仅支持键盘转发功能。

Key Mime Pi 通过 USB 连接到目标机器,注册为键盘设备。它也提供了一个 Web 页面,通过 JavaScript 监听键盘事件。当用户输入时,Key Mime Pi 会捕获这些按键事件,并通过其模拟的 USB 键盘将其转换为实际键盘动作,从而使目标计算机显示相应字符。关于这一运行机制,我已经在之前的文章中详细说明

关于采集视频的挑战 🔗︎

如果不知道屏幕上显示的是什么,那么键盘指令转发基本没什么用。所以我的下一步就是采集我服务器的显示,并将它输出到树莓派上,然后渲染视频到浏览器。

关于视频采集的第一个尝试,我使用的是 Lenkeng LKV373A HDMI extender。Daniel Kučera(aka danman)对这款设备的逆向工程做了很多 贡献。这款设备可以在 eBay 上从中国商户那花 $40 买到,这是我当时能找到的最佳选择。

Lenkeng LKV373A HDMI延长器照片

Lenkeng LKV373A HDMI extender是我尝试的第一款HDMI视频采集器

因为 LKV373A 发射器不是专业的视频采集设备,所以采集视频的过程非常棘手。LKV373A 发射器预期的使用方式是和 LKV373A 接收器进行配对,将网络流转换为 HDMI 输出。在 danman 的研究中,他发现一种方式去拦截 & 捕获视频输出流,但是因为 LKV373A 使用了非标准的 RTP 协议,导致绝大多数视频工具无法识别其格式。

幸运的是,danman 为 ffmpeg 贡献了一个补丁,使得能够处理 LKV377A 设备的异常行为,因此我得以通过 ffmpeg 的视频播放器成功渲染该视频流。

ffplay -i udp://239.255.42.42:5004
ffplay 渲染从LKVA373A获取的视频流

使用ffplay渲染从LKVA373A获取的视频流

这一步,我第一次遇到了贯穿整个项目的难题:延迟。目标计算机和我桌面上播放的回传视频之间有将近一秒的延迟。

Lenkeng LKV373A HDMI延长器延迟测试照片

在视频流还没经过任何处理(比如转码或重新压缩)之前,LKV373A 发射器本身就已经造成了大约 838 毫秒的延迟

我尝试了很多 ffplay 命令去降低延迟,但是始终没有突破 800 ms 大关。这还是在配置比较高的台式机上实现的,如果使用树莓派的话恐怕表现会更差。

幸运的是,我偶然间发现了一个更好的解决方案。

HDMI to USB 转换器 🔗︎

当我浏览推特时,我看到了 Arsenio Dev 的一条推文,关于他刚刚购买的一个低价 HDMI to USB 转换器。

Arsenio Dev的推文截图

一篇来自Arsenio Dev的推特 启发我找到了更好的视频采集方式

1080p & 30 帧/s 采集视频,这个描述有点难以置信,我马上下单了。仅售 $11 并且包邮。我甚至不知道它的品牌名,所以之后我就叫它“HDMI 转换器”吧。类似的商品有很多,这类商品的关键点在于 MS2109 芯片

eBay 上售价 11.20 美元的 HDMI 转换器截图

在 eBay 上 $11.20 就可以买到这款 HDMI to USB 转换器,并且包邮

我收到快递之后,体验出乎意料地好。不需要任何额外的操作,当我把它插入树莓派之后,立即被识别为 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 转换器
        /dev/video0
        /dev/video1

短短几分钟内,我就能捕获并重新推流(/dev/video0 -> ffmpeg -> udp stream)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 转换器的大小接近 U 盘,并且只需要一个 USB 接口。

Lenkeng LKV373A与HDMI转换器对比

Lenkeng LKV373A HDMI延长器(左)比HDMI转换器(右)更大,需要更多连接线

现在就只有一个问题了,还是延迟。从树莓派获取的视频流比源计算机慢了 7~10 秒。

使用ffmpeg从树莓派流式传输视频的延迟对比

使用ffmpeg从树莓派流化视频时,视频延迟最高达到了10秒

我不确定延迟来自 HDMI 转换器、树莓派上的 ffmpeg,还是我桌面端的 ffplay。Arsenio Dev 说延迟只有 20 毫秒,所以似乎可以通过深入研究 ffmpeg 那晦涩难懂的命令行参数 去降低延迟。

又一次的幸运让我免于承担那项令人痛苦的任务。

浏览一个相似的项目 🔗︎

当我发布了之前关于 Key Mime Pi 的帖子之后,我收到了来自 Max Davaev 的帖子,他鼓励我尝试他的项目 Pi-KVM

Max的评论截图:你好:) 看看这个项目:https://github.com/pikvm/pikvm 我们已经完成和调试了很多功能

Max Devaev 指出它已经存在的 Pi-KVM 项目。

GPIO引脚照片

我之前使用面包板的经历包括不小心把它们熔化了

我之前粗略地看过 Pi-KVM,但是这个项目涉及到 面包板 和焊接,这让我望而却步。

在 Max 的建议下,我再次看了一下 Pi-KVM 这个项目,我对 Pi-KVM 如何解决延迟问题非常感兴趣。我注意到他采集视频使用的工具是 uStreamer

Note: 通过和 Max 的进一步交流,我了解到 Pi-KVM 支持在不需要焊接和面包板的条件下构建。

uStreamer:一款超高速视频流媒体工具 🔗︎

你是否遇到过一款优秀的工具,它甚至解决了你没有预料到的问题?

使用 uStreamer 之后,立马将延迟从 8 秒降低到 500~600 ms。并且顺带解决了我很多额外的工作。

使用uStreamer和HDMI转换器实现500毫秒延迟

uStreamer将我的延迟降低了15倍

使用 uStreamer 之前,我不确定怎样把 ffmpeg 采集的视频呈现在用户的浏览器上,但是我知道这是可以做到的。我测试了这篇 教程,它演示了使用 HLS 协议将 ffmpeg 视频传输到 nginx,但是这引入了更多的延迟。并且遗留了一些问题,如何在 HDMI 线缆插拔时启停流媒体、如何将视频转码为浏览器友好的格式。

uStreamer 解决了所有的问题。它本身就运行了一个最小化的HTTP服务器,直接提供浏览器原生支持的Motion JPEG格式。我既无需折腾 HLS 流媒体配置,也不必调试 ffmpeg 与 nginx 的对接问题。

这么全能的工具,我想是 Max fork 了一个成熟的项目,然后做了改造。但是我错了,这位大佬使用 C 语言写了他自己的 视频编码器,只为能使树莓派的性能最大化。我立马为这个项目进行 捐献,并且也想邀请任何使用这个项目的人捐献。

改进视频传输延迟 🔗︎

uStreamer 将延迟从 10 秒减少到约 600 ms。这是一个大的飞跃,但是这个级别的延迟仍是可感知的。我告诉 Max 我有意进一步捐赠项目,如果他能找到进一步提升性能的方式,所以我们进行了聊天。

Max 对 HDMI 转换器感兴趣,因为他从未见过这种特殊设备。他邀请我使用 tmate 创建一个共享终端会话,以便他能远程访问我的树莓派。 Max was interested in the HDMI dongle I was using since he’d never seen that particular device. He invited me to create a shared shell session using tmate so that he could access my Pi remotely.

Max通过tmate提供帮助的对话截图

Max 主动提出要么帮助我改善延迟,要么陷害我犯下联邦罪行。幸运的是,他最终选择了前者。

经过几分钟的测试后,Max 跑了一个 [v4l2-ctl utility],看到一行输出之后,他非常兴奋,但是我完全摸不着头脑:

$ 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,让它跳过编码直接传输视频流。

消除重新编码步骤后200毫秒延迟的照片

跳过树莓派上的额外重新编码步骤,延迟从600毫秒降低到200毫秒

延迟从 600 ms 降低到了 200 ms。虽然算不上即时,但是在我使用几分钟之后,几乎感觉不到延迟。

TinyPilot 实机演示 🔗︎

还记得我在文章开头对这个项目的愿景么?我想在我的无头虚拟机服务器在系统启动之前就可以访问它,是的我做到了。

我迭代了 Key Mime Pi 项目,开发了一个集成视频采集功能的新 Web 界面。

今年我搭建了一台新的无头虚拟机服务器,并使用 TinyPilot 安装了 Proxmox——这是一款用于管理虚拟机的开源虚拟化平台,并且提供了 Web 页面去管理虚拟机。

TinyPilot 允许我去通过浏览器管理整个系统的安装过程。这比起以往来回搬动电脑、不断插拔线缆的旧方式,体验确实愉悦得多。

如何去构建你自己的 TinyPilot 🔗︎

零件清单 🔗︎

安装 Raspberry Pi OS Lite 操作系统 🔗︎

开始之前,需要在 microSD 卡上安装 Raspberry Pi OS Lite(之前被称为 Raspbian)。

Warning: TinyPilot 不支持最新的 Raspberry Pi 操作系统。系统版本需要为 Raspberry Pi OS Bullseye (32-bit)

Rufus软件截图

我使用 Rufus 来写入树莓派的 micro SD 卡,但任何磁盘映像工具都可以

通过在 microSD 卡的 boot 分区创建一个 ssh 文件,以启动 SSH 访问功能。如果你想通过无线网连接,还需要一个 wpa_supplicant.conf 文件

当你准备好 microSD 卡之后,将它插入树莓派中。

安装一个机壳 (可选的) 🔗︎

树莓派 4 是出了名的发热量大。你可以在没有散热的条件下运行,但是长时间运行可能遇到稳定性问题。

我选择了最小化的机壳,它的价格低,并且有被动散热,而且它没有风扇,这降低了硬件的复杂性:

树莓派极简铝制外壳

这款极简铝制外壳能很好地为树莓派散热,且没有风扇的复杂性

通过USB连接到目标机器 🔗︎

为了让 TinyPilot 可以模拟键盘,需要将树莓派的 USB-C 接口与目标机的 USB-A 接口相连:

Note: 最好使用 USB 3.0 接口,因为它们可以为 Pi 提供更多的电力。

连接HDMI转换器 🔗︎

请将 HDMI 转换器插入树莓派的任一 USB 端口。随后用 HDMI 线连接 HDMI 转换器,并将另一端接入目标电脑的显示输出接口。

Note: 如果你的目标机器没有 HDMI 输出接口,你可以使用 DP 转 HDMI 线或者 DVI 转 HDMI 线,尽管我没有测试这些。

连接网口 🔗︎

如果你通过有线局域网连接树莓派,请将网线插入树莓派的以太网端口:

网线连接到树莓派设备的照片

将网线连接到你的树莓派

Note: 如果你在 之前 通过添加 wpa_supplicant.conf 文件配置了无线网络,那么你可以跳过这一步。

安装 TinyPilot 软件 🔗︎

通过 SSH 连接到树莓派设备(树莓派操作系统的默认登录凭证是 pi / raspberry),然后执行下面这段命令:

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

如果你对一个随便从网上下载的脚本有怀疑,我鼓励你去检查这个脚本的 源代码

该脚本引导一个自包含的 Ansible 环境,其中集成了我的 TinyPilot Ansible 角色。它会安装四项在每次启动时自动运行的服务:

  • nginx: 一个受欢迎的开源 Web 服务器
  • ustreamer: 一个轻量级的HTTP 视频流服务器
  • usb-gadget: 一个启动树莓派 USB gadget mode 的脚本,开启后允许树莓派模拟 USB 设备
  • tinypilot: 我为 TinyPilot 创建的 Web 界面
译者注: 现在的 TinyPilot 项目已经抛弃了 Ansible

使用 TinyPilot 🔗︎

在运行完脚本之后,TinyPilot 就可以使用了,你可以通过这个地址访问:

TinyPilot网页界面截图

设置完成后,你可以在本地网络中通过 http://raspberrypi/ 访问 TinyPilot 的网页界面

电源问题 🔗︎

这套设备最大的限制就是电源。依赖目标机器的电源意味着当目标机器关机时,树莓派会遭遇意外断电。

更进一步,树莓派需要 3 A 的稳定电流,但是它也可以在低供电下运行。USB 3.0 接口只能提供 0.9 A 的供电,USB 2.0 仅仅能提供 0.5 A 的供电,这就是为什么我们可以在树莓派的系统日志中看到这些警告:

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

为了解决这个问题,我与一家工程公司合作制作了一款定制的电路板,将树莓派的 USB-C 端口分为两个。一个端口用来接收 USB 供电,让树莓派可以获得稳定的 3 A 电流。另一个端口用来接收 USB 数据输出,让树莓派可以模拟 USB 键盘。

重要的是,电源连接器的数据端口不包括 USB 电源线。这确保了计算机电源与树莓派电源之间的电压差不会引发有害的电力回流。

Note: 没有合适的连接器,在树莓派连接至电脑时使用外部电源供电可能导致硬件损坏。更多详情请 参阅 TinyPilot 维基页面

源代码 🔗︎

TinyPilot 的软件是开源的,开源协议是 MIT license

成品 TinyPilot 设备 🔗︎

TinyPilot 官网上可直接购买成品设备。我虽是 TinyPilot 公司的初创创始人,但已于 2024 年 4 月将业务转让,如今除作为热心用户外与该公司再无关联。


特别感谢 Max Devaev 在 uStreamer 项目上的卓越贡献,以及他为 TinyPilot 所付出的努力。