欢迎光临
我们一直在努力

旧笔记本变身家庭摄像头:在飞牛 fnOS 上用 go2rtc 驱动内置摄像头

旧笔记本变身家庭摄像头:在飞牛 fnOS 上用 go2rtc 驱动内置摄像头

本文记录一次真实的折腾过程:一台不再合盖的旧笔记本已经安装了飞牛 fnOS,我们继续利用它的内置摄像头,通过 Docker 和 go2rtc 搭建一个可在浏览器中查看的家庭视频流。

一、目标与最终效果

这次实现的目标很明确:

  • 让 fnOS 识别并读取笔记本内置摄像头;
  • 不在 fnOS 宿主系统中堆积不必要的软件包;
  • 使用 Docker 运行轻量级视频服务;
  • 把摄像头的原始画面转成浏览器可播放的视频流;
  • 给 Web 管理界面加上认证,不使用无密码服务;
  • 让 fnOS 识别容器的 Web 端口,通过 FN Connect 从外网访问。

完成后的处理链路如下:

联想笔记本内置摄像头


Linux V4L2(/dev/video0)
│ YUYV 原始帧

go2rtc 容器内的 FFmpeg
│ 转码为 H.264

go2rtc WebRTC / MSE / Web 播放界面

├── 局域网 IP:1984

└── FN Connect HTTPS 代理入口

本文已完成局域网视频查看,并在电脑浏览器中验证了 FN Connect 外网访问。FN Connect 提供的是经 fnOS 授权的 HTTPS 代理入口,不是把 go2rtc 端口直接暴露到公网。

二、测试环境

本次使用的环境如下:

项目实际环境
NAS 系统 飞牛 fnOS,底层为 Debian 12 Bookworm
Linux 内核 6.18.18 x86_64
Docker 28.5.2
Docker Compose v5.0.2
摄像头 Syntek Lenovo EasyCamera,USB ID 174f:1158
视频设备 /dev/video0 和 /dev/video1
视频服务 alexxit/go2rtc:latest

三、先判断 Linux 是否识别摄像头

开始部署之前,第一件事不是安装软件,而是确认硬件链路是否成立。

通过 fnOS 的 SSH 进入系统后,执行:

uname -a
cat /etc/os-release
lsusb
ls -l /dev/video*

关键输出为:

Bus 001 Device 002: ID 174f:1158 Syntek Lenovo EasyCamera

crw-rw—- 1 root video … /dev/video0
crw-rw—- 1 root video … /dev/video1

这说明三件事:

  • USB 总线已经识别到内置摄像头;
  • Linux UVC/V4L2 驱动已经为它创建设备节点;
  • 不需要额外安装 Windows 风格的「厂商驱动」。
  • V4L2 是什么

    V4L2 的全称是 Video4Linux2,是 Linux 下摄像头、采集卡等视频设备的标准接口。应用程序不需要直接理解 USB 摄像头协议,只需要打开 /dev/video0,并通过 V4L2 设置分辨率、帧率和像素格式。

    四、为什么同一个摄像头会有 video0 和 video1

    一个 USB 摄像头出现多个 /dev/videoX 节点并不罕见。它们可能分别代表:

    • 可读取画面的视频采集节点;
    • 摄像头元数据或辅助功能节点;
    • 不同像素格式或不同功能的独立接口。

    本次对两个节点分别执行 FFmpeg 探测:

    docker run –rm \\
    –device /dev/video0:/dev/video0 \\
    –entrypoint ffmpeg \\
    alexxit/go2rtc:latest \\
    -hide_banner -f v4l2 -list_formats all -i /dev/video0

    /dev/video0 能够列出画面格式:

    Raw : yuyv422 : YUYV 4:2:2 : 640×480 640×360 320×240 160×120

    而探测 /dev/video1 时返回:

    ioctl(VIDIOC_G_INPUT): Not a tty

    因此可以确定:

    • /dev/video0 是实际的画面采集节点;
    • /dev/video1 不支持普通视频输入所需的 VIDIOC_G_INPUT 操作,不应映射给视频服务。

    /dev/video0 探测命令末尾也出现了 Error opening input file。这条命令的目的只是让 FFmpeg 枚举格式,并没有配置实际输出;既然前面已经成功列出 YUYV 和分辨率,就不能根据末尾的错误认定设备不存在。后续的实际播放也证明了 /dev/video0 选择正确。

    为什么用临时 Docker 容器探测

    fnOS 宿主上没有安装 v4l2-ctl。安装 v4l-utils 当然也可以,但这次直接复用 go2rtc 镜像内自带的 FFmpeg,并使用 –rm 创建一次性容器。命令结束后容器自动删除,宿主系统不需要新增软件包。

    五、为什么要转码为 H.264

    这颗 EasyCamera 枚举出来的是 yuyv422,也就是未压缩的 YUYV 4:2:2 原始帧,而不是摄像头内部已经压缩好的 H.264。

    640×480、30 fps、16 bit/像素的理论原始数据量约为:

    640 × 480 × 30 × 16 ≈ 147,456,000 bit/s

    也就是约 147 Mbit/s,还未计算协议开销。如果直接在网络上传输原始帧,既浪费带宽,浏览器也不一定能直接播放。

    因此本方案让 go2rtc 调用 FFmpeg,把 YUYV 转码为 H.264:

    ffmpeg:device?video=/dev/video0
    &input_format=yuyv422
    &video_size=640×480
    &framerate=15
    #video=h264

    初始帧率选择 15 fps,是为了降低旧笔记本的 CPU 压力。对「偶尔看看家里情况」这种场景,15 fps 通常已经足够。

    六、为什么选择 go2rtc

    go2rtc 是一个面向摄像头和家庭自动化场景的视频协议转换工具,支持 RTSP、WebRTC、HLS、MP4 等多种输入输出方式。官方 Docker 镜像还内置了 FFmpeg,因此非常适合当前的单摄像头、低复杂度场景。

    相比完整的 NVR 系统,这里暂时不需要持续录像、AI 识别或多路摄像头管理,因此不必一开始就部署 Frigate 等更重的方案。

    go2rtc 的官方文档:

    • go2rtc 项目主页
    • Docker 部署说明
    • FFmpeg 摄像头输入说明

    七、第一次启动为什么不断重启

    第一次启动时,我们尝试在 docker run 命令的镜像名后直接追加多个 -c 配置参数。容器启动后,fnOS 的 Docker 界面显示它一直重启。

    通过以下命令停止重启并保留现场:

    docker update –restart=no go2rtc-camera
    docker stop go2rtc-camera
    docker logs –tail 100 go2rtc-camera
    docker inspect go2rtc-camera \\
    –format '退出码={{.State.ExitCode}} 错误={{.State.Error}} 启动参数={{json .Config.Cmd}}'

    日志非常明确:

    [FATAL tini (7)] exec -c failed: No such file or directory

    容器退出码为 127,通常表示要执行的命令不存在。

    根本原因:Docker 镜像的 ENTRYPOINT 与 CMD

    Docker 镜像可以同时定义:

    • ENTRYPOINT:容器启动时固定执行的入口;
    • CMD:传给入口的默认命令或参数。

    go2rtc 镜像使用 tini 作为容器的一号进程,默认 CMD 中会包含真正的 go2rtc 启动命令。

    但是,docker run IMAGE … 中写在镜像名后面的内容会替换镜像原有的 CMD。当时的命令使 CMD 变成了:

    ["-c", "streams.home_camera=…", "-c", "api.username=…", "…"]

    于是 tini 尝试把 -c 当成可执行程序,系统当然找不到名为 -c 的程序,因此立即以 127 退出。

    容器还配置了:

    restart: unlessstopped

    所以整个循环就变成了:

    tini 启动 → 尝试执行 -c → 退出码 127
    ↑ │
    └───── Docker 自动重启 ────────┘

    这也是为什么在 Docker 界面上只能看到「不断重启」,但摄像头指示灯始终不亮:程序还没有运行到打开摄像头的阶段。

    正确处理方式

    修复时没有继续堆叠命令行参数,而是回到官方更推荐的方式:

  • 把 go2rtc 配置写入独立的 go2rtc.yaml;
  • 把配置目录挂载到容器的 /config;
  • 不覆盖镜像的默认 CMD;
  • 使用 Docker Compose 管理容器生命周期。
  • 八、最终配置

    目录结构如下:

    /root/go2rtc-camera/
    ├── compose.yaml
    ├── login.txt
    └── config/
    └── go2rtc.yaml

    go2rtc.yaml

    api:
    listen: ":1984"
    username: "your_camera_user"
    password: "replace_with_a_strong_password"
    local_auth: true

    rtsp:
    listen: "127.0.0.1:8554"

    webrtc:
    listen: ":8555"

    streams:
    home_camera: "ffmpeg:device?video=/dev/video0&input_format=yuyv422&video_size=640×480&framerate=15#video=h264"

    log:
    level: info

    这里有几个重要细节:

    • local_auth: true:即使请求被视为本地请求,也要求认证;
    • RTSP 只监听 127.0.0.1:8554,不向局域网暴露;
    • WebRTC 使用 8555 端口;
    • 摄像头选择经过探测的 /dev/video0;
    • 输入格式、分辨率和帧率都是根据实际设备能力设置的;
    • 用户名和密码必须换成自己的值,不应把真实密码发布到博客或 Git 仓库。

    compose.yaml

    services:
    go2rtc-camera:
    image: alexxit/go2rtc:latest
    container_name: go2rtccamera
    restart: unlessstopped
    ports:
    "1984:1984/tcp"
    "8555:8555/tcp"
    "8555:8555/udp"
    devices:
    /dev/video0:/dev/video0
    environment:
    TZ: Asia/Shanghai
    volumes:
    ./config:/config:ro

    Compose 配置的原理

    devices 只向容器映射 /dev/video0,比直接赋予容器 privileged 权限更小。本方案使用 CPU 转码,因此不需要为了显卡加速开启特权模式。

    初版配置使用过 network_mode: host。这种模式下容器直接共享宿主网络栈,局域网播放很方便,但 Docker 不会真正建立 ports 发布规则。即使在配置中填入端口,docker port go2rtc-camera 仍然没有输出,因为 Docker 官方明确规定 host 网络下端口发布不生效。

    fnOS 需要根据 Docker 的有效端口映射生成容器访问入口,因此最终改用默认 bridge 网络,并显式发布 1984 和 8555。1984 是 go2rtc Web/API 端口;8555 是 WebRTC 的 TCP/UDP 媒体端口,它本来就不是可以用浏览器直接打开的网页。参考 Docker Host 网络说明。

    ./config:/config:ro 将配置目录以只读方式挂载。这意味着要修改配置时,应在 fnOS 宿主上编辑 YAML 并重启容器,而不是从 Web 页面直接覆盖文件。

    九、启动和验证

    在配置目录中启动:

    cd /root/go2rtc-camera
    docker compose up -d

    检查状态和日志:

    docker compose ps
    docker compose logs –tail 50
    docker port go2rtc-camera

    切换到 bridge 并重建容器后,docker port 应出现真正的映射:

    1984/tcp -> 0.0.0.0:1984
    8555/tcp -> 0.0.0.0:8555
    8555/udp -> 0.0.0.0:8555

    然后在同一局域网的浏览器中打开:

    http://<fnOS 局域网 IP>:1984

    输入 go2rtc.yaml 中配置的用户名和密码,再选择 home_camera 即可播放。

    通过 FN Connect 外网访问

    开启 FN Connect 并登录 fnOS 后,从 Docker 界面找到 go2rtc-camera,再点击 1984 对应的访问入口。fnOS 会生成受当前登录会话保护的 HTTPS 代理地址。这个地址不等同于在 FN ID 后手工追加 :1984,应当从 fnOS 界面进入。

    外网播放可以显式使用 MSE 模式:

    /stream.html?src=home_camera&mode=mse

    WebRTC 会另外尝试使用 8555 TCP/UDP 传输媒体,而 MSE 通过 1984 上的同源 WebSocket 传输 H.264,更适合 HTTPS 代理链路。实际验证中,电脑浏览器可以经 FN Connect 打开页面并观看视频。关于 FN Connect 的直连、P2P 和中继机制,可参考 飞牛官方远程访问说明。

    运行时可以查看容器资源消耗:

    docker stats –no-stream go2rtc-camera

    如果后续将帧率调高到 30 fps,应重新观察 CPU 占用、温度和播放稳定性,不要只根据「能打开画面」判定长期运行没有问题。

    十、修改登录用户名和密码

    直接编辑宿主上的配置:

    nano /root/go2rtc-camera/config/go2rtc.yaml

    修改:

    username: "your_camera_user"
    password: "your_new_strong_password"

    保存后重启:

    cd /root/go2rtc-camera
    docker compose restart

    浏览器可能会缓存 HTTP Basic Auth 凭据。如果修改后没有重新弹出登录框,可以关闭相关页面或使用无痕窗口重新打开。

    十一、安全注意事项

    go2rtc 的 Web API 不只是一个静态播放页面,还包含配置和流管理能力。因此,「能看画面」不等于「可以无保护暴露到互联网」。

    建议至少遵守以下原则:

  • 不要在路由器上把 1984 或 8555 端口直接转发到公网;
  • 始终为 Web API 设置独立用户名和强密码;
  • 摄像头密码不要和 fnOS 管理员密码相同;
  • 不要在博客、截图或 Git 仓库中发布真实凭据;
  • 外网查看优先使用 FN Connect 的授权入口,或 WireGuard、Tailscale 类可控虚拟组网;
  • 如果使用反向代理,应同时考虑 HTTPS、WebSocket、WebRTC 端口和访问控制,不要只代理一个普通 HTTP 页面。
  • go2rtc 官方也明确提醒,未正确保护的 API 可能让攻击者访问摄像头甚至服务器。可参考 go2rtc HTTP API 安全说明。

    十二、这次实践的几个经验

    1. 先验证硬件链路,再安装服务

    如果 /dev/video0 根本不存在,部署再多容器也无法解决问题。从 USB 识别、设备节点到支持格式逐层确认,可以快速缩小故障范围。

    2. 设备节点存在,不等于它一定能输出画面

    /dev/video1 就是很好的例子。多设备节点必须通过 V4L2 能力探测区分,不应简单地把所有 /dev/video* 全部映射给容器。

    3. 摄像头输出格式决定系统负载

    如果摄像头本身能输出 H.264,go2rtc 可能只需要转封装,CPU 开销很小。当摄像头只能输出 YUYV 时,宿主必须承担实时编码成本。因此分辨率和帧率不能凭空填写,应根据设备能力和机器负载确定。

    4. Docker 重启循环首先看退出码和真实启动参数

    容器「一直重启」只是外在现象。本次通过 docker logs 看到 exec -c failed,再通过 docker inspect 看到被覆盖后的 CMD,才确定问题和摄像头、端口或密码都无关。

    5. 简单场景不必一开始就上完整 NVR

    本次需求主要是实时看画面,go2rtc 已经能够完成采集、转码和浏览器播放。如果未来需要移动侦测、录像存储、时间线回放或 AI 识别,再引入 motionEye、Frigate 或其他 NVR 系统更合理。

    十三、后续可以继续做什么

    在当前局域网播放和电脑端 FN Connect 外网访问稳定之后,可以按需增加:

    • 进一步验证不同浏览器和客户端下的播放兼容性;
    • 如果需要固定访问地址或更通用的客户端支持,再配置 VPN 或虚拟组网;
    • 测试并调整 15/20/30 fps,在流畅度与 CPU 负载之间取舍;
    • 为笔记本设置来电自启和异常断电恢复;
    • 测试长时间运行时的 CPU 温度和风扇噪声;
    • 增加定时截图、移动侦测或录像功能;
    • 如果 CPU 占用成为瓶颈,再评估 Intel VAAPI 硬件编码。

    结语

    这次改造没有添加新硬件,只是把旧笔记本上原本闲置的摄像头重新利用了起来。整个过程的核心不是复制一份 Docker Compose,而是从硬件识别、V4L2 设备节点、像素格式、实时转码一直追踪到 Docker 的进程启动语义。

    也正是这个过程,让一个「容器一直重启」的模糊现象,最终变成了一个可以被日志、退出码和启动参数完整解释的具体问题。

    赞(0)
    未经允许不得转载:171主机测评 » 旧笔记本变身家庭摄像头:在飞牛 fnOS 上用 go2rtc 驱动内置摄像头
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址