首图是目前的家庭网络设备拓扑图。虽然是教程贴,还是记录下前因后果。
看片困境 前段时间买了低功耗小主机,关掉了威联通 NAS,客厅如何看电视就成了问题,看了不少经验分享贴都推荐五十块的二手魔百盒。闲鱼下单了 s905l3a 带 wifi 的商家版本,预装了常用的电视 app,配合红外遥控确实是老年人友好的解决方案。 开机后发现按键操作时常失灵,而且操作和播放视频过程中频繁自启或者退回桌面。联系商家说可能是电源的问题,这个是旧华为路由器闲置下来的。尝试跟光猫的电源(高 0.5A)交换,情况有所好转,但依然会概率性重启。一番交涉,商家爽快的答应换货。
最坑的是,BBLL 能装上,jellyfin TV 却不行。网上也看到有人 po 出同样的问题,原因是这种在运营商基础上魔改的 ROM 缺少很多系统模块。有大佬给出了 patch ,但需要自己动手编译并且不保证有 bug,遂放弃。 当然,还有刷机这条路,买这个盒子也是图他 ROM 生态丰富。偶然间看到盒子店铺的一条评论,想再刷回来的话还要再掏钱或者提前备份镜像(不会啊),还是放弃吧。所以换货回来只是简单检查了下重启的毛病,就没有继续折腾的兴致了,扔到卧室给小朋友放放动画片也挺好。
前期摸索 所以事情又回到原点,客厅的电视用什么播放器。最终,还是打上了小主机的主意。因为一开始只打算做网络服务,装的是没有桌面程序的 Ubuntu server 系统,不过 N100 算比较新的 CPU,为了提高驱动兼容性上了最新的 **Ubuntu 24.10 (Oracular Oriole)**,据说明年 Plucky Puffin 发布的时候可以无痛升级。
所有 linux TV 播放器里面,kodi 绝对是老大哥 ,生态完整、资料好找,安装方式也多种多样,最简单的当然是 libreElec,开箱即用,但考虑到要把系统重装为 PVE,也没有 openwrt 的需求,还是打算宿主机直装的方式。一开始考虑 docker,但不是版本太老 就是没啥人用 ,出问题的概率很大。
幸运的是,找到两篇(1 ,2 )在 server 版 ubuntu 上最小安装 kodi 的教程;遗憾的是,他们用的 GUI 框架都是比较古老的 X11,在最新内核下需要安装很多依赖并且参在兼容性风险。另外这个一键安装脚本 也是差不多的思路。关于 X11、Wayland 和 GBM 的区别可以看这里 的解释。
进一步查找,官方论坛的这个帖子 给出了重要线索,所以首先确认硬件驱动没问题(参考 1 ,2 ),然后根据官方文档直接安装。
具体流程 硬件和驱动情况如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ➜ ~ uname -a Linux n100 6.11.0-9-generic #9-Ubuntu SMP PREEMPT_DYNAMIC Mon Oct 14 13:19:59 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux ➜ ~ sudo lspci -v | grep i915 Kernel driver in use: i915 Kernel modules: i915, xe➜ ~ sudo lshw | grep i915 configuration: depth=32 driver=i915 latency=0 mode=1920x1080 resolution=1920,1080 visual=truecolor xres=1920 yres=1080➜ ~ sudo dmesg | grep i915 [ 13.326033] i915 0000:00:02.0: [drm] Found ALDERLAKE_P/ADL-N (device ID 46d1) display version 13.00 stepping D0 [ 13.326927] i915 0000:00:02.0: [drm] VT-d active for gfx access [ 13.370261] i915 0000:00:02.0: vgaarb: deactivate vga console [ 13.371357] i915 0000:00:02.0: [drm] Using Transparent Hugepages [ 13.371930] i915 0000:00:02.0: vgaarb: VGA decodes changed: olddecodes=io+mem,decodes=io+mem:owns=io+mem [ 13.374690] i915 0000:00:02.0: [drm] Finished loading DMC firmware i915/adlp_dmc.bin (v2.20)[ 13.587503] i915 0000:00:02.0: [drm] GT0: GuC firmware i915/tgl_guc_70.bin version 70.29.2 [ 13.587515] i915 0000:00:02.0: [drm] GT0: HuC firmware i915/tgl_huc.bin version 7.9.3 [ 13.592056] i915 0000:00:02.0: [drm] GT0: HuC: authenticated for all workloads [ 13.592757] i915 0000:00:02.0: [drm] GT0: GUC: submission enabled [ 13.592762] i915 0000:00:02.0: [drm] GT0: GUC: SLPC enabled [ 13.593204] i915 0000:00:02.0: [drm] GT0: GUC: RC enabled [ 13.594830] mei_pxp 0000:00:16.0-fbf6fcf1-96cf-4e2e-a6a6-1bab8cbe36b1: bound 0000:00:02.0 (ops i915_pxp_tee_component_ops [i915]) [ 13.595027] i915 0000:00:02.0: [drm] Protected Xe Path (PXP) protected content support initialized [ 13.595034] mei_hdcp 0000:00:16.0-b638ab7e-94e2-4ea2-a552-d1c54b627f04: bound 0000:00:02.0 (ops i915_hdcp_ops [i915]) [ 13.630189] [drm] Initialized i915 1.6.0 for 0000:00:02.0 on minor 1 [ 13.706559] fbcon: i915drmfb (fb0) is primary device [ 13.784629] i915 0000:00:02.0: [drm] fb0: i915drmfb frame buffer device [ 13.796441] sof-audio-pci-intel-tgl 0000:00:1f.3: bound 0000:00:02.0 (ops i915_audio_component_bind_ops [i915])
看上去万事具备了,不需要手动安装驱动之类的。根据官方文档 add-apt-repository -r ppa:team-xbmc/ppa
却遇到问题,提示“The repository ‘https://ppa.launchpadcontent.net/team-xbmc/ppa/ubuntu oracular Release’ does not have a Release file”,手动修改/etc/apt/sources.list.d/team-xbmc-ubuntu-ppa-oracular.sources
,把oracular
降级为 23.04 的lunar
是可以获取到源了,但却少了kodi-gbm
包,且不知道会不会有其他隐性问题。从新闻看到,kodi 团队从今年 5 月开始就停止维护 PPA 源了,索性参考文档 手动编译。另外根据 reddit 网友提供的信息,手动编译可以设置 HDR 直通(虽然家里的老电视只支持 SDR)。
同样的问题,安装前置依赖 过程中无法添加 xbmc-nightly 源,只能手动安装,大概占掉 1G 的硬盘空间。配置之前还要安装 libdisplay-info:
cd ~ && wget https://gitlab.freedesktop.org/emersion/libdisplay-info/-/archive/0.2.0/libdisplay-info-0.2.0.tar.gz tar xzf libdisplay-info-0.2.0.tar.gz && cd libdisplay-info-0.2.0 mkdir build && cd build meson setup --prefix=/usr --buildtype=release ninja sudo ninja install cd ~/kodi-build cmake ../kodi -DCMAKE_INSTALL_PREFIX=/usr/local -DCORE_PLATFORM_NAME=gbm -DAPP_RENDER_SYSTEM=gles -DENABLE_VAAPI=ON
理所当然的报错了:
CMake Error at /usr/share/cmake-3.30 /Modules/FindPackageHandleStandardArgs.cmake:233 (message ): Could NOT find PCRE (missing: PCRE_LIBRARY PCRE_INCLUDE_DIR) Call Stack (most recent call first): /usr/share/cmake-3.30 /Modules/FindPackageHandleStandardArgs.cmake:603 (_FPHSA_FAILURE_MESSAGE) cmake/modules/FindPCRE.cmake:126 (find_package_handle_standard_args) cmake/scripts/common/Macros.cmake:403 (find_package ) cmake/scripts/common/Macros.cmake:417 (find_package_with_ver) CMakeLists.txt:261 (core_require_dep) -- Configuring incomplete, errors occurred!
搜索发现,最近的更新从 pcre 切换到了 pcre2,而之前安装的就是 libpcre2-dev,而 ubuntu apt 仓库里根本就没有 libpcre-dev。所以源码下载地址从release 切换到master 。顺利通过配置,执行编译,然后安装必要插件并执行安装。
cmake --build . -- VERBOSE=1 -j$(getconf _NPROCESSORS_ONLN) cd ~/kodi sudo make -j$(getconf _NPROCESSORS_ONLN) -C tools/depends/target/binary-addons PREFIX=/usr/local ADDONS="audioencoder.flac audioencoder.lame audioencoder.vorbis audioencoder.wav" sudo make install cd ../kodi-build && sudo make install
注意编译过程中会从网络下载依赖,编译期间最好挂上梯子。
主程序编译耗时 30 分钟,成功之后生成 kodi-gbm 可执行文件,使用 sudo ./kodi-gbm
进行测试。播放压制视频时 CPU 占用在 10%~45% 之间,拖动丝滑。注意,如果没有给当前用户赋予足够的权限(特别是 render 和 input 群组),一定要用 root 执行,否则可能无法硬解或使用键鼠。成功执行后会在用户根目录下生成 .kodi 文件夹,用于存放程序相关数据。
后续问题 远程遥控 app 手机上安装 Yatse 并连上局域网,小主机插上鼠标,根据文档 打开远程控制功能。成功登录之后就可以拔掉鼠标或键盘了。Pro 版增加了投屏、jellyfin/emby/plex 客户端、离线播放等功能,仅售 3.49 刀。经测试,实际是调用 vlc player 或 MX player 进行播放,本身只是海报墙的作用。DLNA 的转码功能要另外收费,另外云转码也不是很实用。所以值不值就见仁见智了。
另外比较坑的是电源菜单第一项是直接关闭主机,而不是退出程序。kodi 没有运行的时候,电源菜单只能通过远程唤醒(WoL)的方式开机(所以要设置 kodi 开机启动)。而我想要的效果是:kodi 服务停止时,电源键可以远程执行systemctl start
命令启动服务;kodi 运行时,电源键默认退出 app 而不是关机。
kodi 跟随电视启停 原本考虑监听 WoL 端口,主机开机状态下收到特征 udp 包则启动 kodi,然而还是不太优雅。于是萌生了另一个想法:轮询判断电视的电源状态,从而启动/关闭 kodi。
这里的关键是/sys/class/drm/card0-HDMI-A-2/status
文件的值。card0 代表显卡,重启后可能变成 card1。HDMI 是接口类型,根据实际情况可能是 DP。A-2
代表接口编号,比如A-1
是 type-c 接口。执行关闭时采用 jsonapi http post 的方式以便安全退出;启动则直接使用 systemctl 命令。根据 AI 给出的答案,首先创建 bash 文件并给予执行权限:
/usr/local/bin/hdmi-monitor.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #!/bin/bash KODI_HOST="usr:pwd@localhost" KODI_PORT="8080" LOG_FILE="/var/log/hdmi-monitor.log" MAX_RETRIES=2 RETRY_DELAY=2log_message () { local message="$1 " local timestamp=$(date '+%Y-%m-%d %H:%M:%S' ) echo "[$timestamp ] $message " >> "$LOG_FILE " }check_kodi_available () { local timeout =1 curl -s -I -m $timeout "http://$KODI_HOST :$KODI_PORT /jsonrpc" >/dev/null return $? }send_alert () { local title="$1 " curl -s -m 30 -X POST "https://cloudflare.notice.service" -d "sp=weixin&t=$message " >/dev/null systemctl stop hdmi-monitor.timer }send_kodi_command () { local method="$1 " local retry_count=0 local success=false while [ $retry_count -lt $MAX_RETRIES ] && [ "$success " = false ]; do if ! check_kodi_available; then log_message "Kodi HTTP server not responding, attempt $(($retry_count + 1) )/$MAX_RETRIES " sleep $RETRY_DELAY retry_count=$((retry_count + 1 )) continue fi response=$(curl -s -X POST \ -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"method\":\"$method \",\"id\":1}" \ "http://$KODI_HOST :$KODI_PORT /jsonrpc" ) if [ $? -eq 0 ] && [ ! -z "$response " ]; then if echo "$response " | grep -q "error" ; then log_message "Error executing Kodi command $method : $response " else success=true break fi else log_message "Failed to execute Kodi command $method , attempt $(($retry_count + 1) )/$MAX_RETRIES " fi retry_count=$((retry_count + 1 )) [ $retry_count -lt $MAX_RETRIES ] && sleep $RETRY_DELAY done if [ "$success " = false ]; then log_message "Failed to execute Kodi command $method after $MAX_RETRIES attempts" return 1 fi return 0 }stop_kodi_gracefully () { log_message "Attempting to stop Kodi gracefully" if ! send_kodi_command "Application.Quit" ; then log_message "Error: Failed to quit Kodi gracefully, forcing service stop" else sleep 2 fi if systemctl is-active --quiet kodi-gbm.service; then if ! systemctl stop kodi-gbm.service; then log_message "Error: Failed to stop kodi-gbm.service" return 1 fi fi log_message "Successfully stopped Kodi and its service" return 0 }touch "$LOG_FILE " 2>/dev/null || { echo "Error: Cannot create or access log file at $LOG_FILE " exit 1 } STATUS=$(cat "/sys/class/drm/card0-HDMI-A-2/status" 2>/dev/null) CARD="card0" if [ $? -ne 0 ]; then STATUS=$(cat "/sys/class/drm/card1-HDMI-A-2/status" 2>/dev/null) CARD="card1" if [ $? -ne 0 ]; then log_message "Error: Could not read HDMI status card0 or card1" send_alert "KODI_HDMI_FAIL" exit 1 fi fi if [ "$STATUS " = "connected" ]; then if ! systemctl is-active --quiet kodi-gbm.service; then if ! systemctl start kodi-gbm.service; then log_message "Error: Failed to start kodi-gbm.service" send_alert "KODI_START_FAIL" exit 1 fi log_message "Successfully start Kodi" fi else if systemctl is-active --quiet kodi-gbm.service; then if ! stop_kodi_gracefully; then exit 1 fi fi fi exit 0
然后是 service 相关文件:
/etc/systemd/system/hdmi-monitor.service [Unit] Description=HDMI Connection Monitor Service After=multi-user.target [Service] Type=oneshot ExecStart=/usr/local/bin/hdmi-monitor.sh [Install] WantedBy=multi-user.target
/etc/systemd/system/hdmi-monitor.timer [Unit] Description=Timer for HDMI Connection Monitor Service [Timer] OnBootSec=30 OnUnitActiveSec=5s Unit=hdmi-monitor.service [Install] WantedBy=timers.target
/etc/logrotate.d/hdmi-monitor /var/log/hdmi-monitor.log { weekly rotate 4 compress missingok notifempty }
启动服务并设置开机自启:
sudo systemctl daemon-reload sudo systemctl enable hdmi-monitor.timer sudo systemctl start hdmi-monitor.timer sudo systemctl status hdmi-monitor.timer
shellCrash 白名单 如果自定义了保留字段,需要把所有局域网段都添加进去,否则本地也有可能走核心。详见讨论
播放无声音 如果运行 kodi 用户添加了 audio 用户组还是没办法调出声音,那么有可能是音频服务组件缺失,具体表现为 audio output device 列表中只有一项 sof-hda-dsp, Analog
。首先安装 alsa 包,依次检查耳机和 HDMI 能否发出声音:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ➜ ~ asudo apt install alsa-base ➜ ~ asudo alsamixer ➜ ~ asudo aplay -l **** List of PLAYBACK Hardware Devices **** card 0: sofhdadsp [sof-hda-dsp], device 0: HDA Analog (*) [] Subdevices: 1/1 Subdevice card 0: sofhdadsp [sof-hda-dsp], device 3: HDMI1 (*) [] Subdevices: 0/1 Subdevice card 0: sofhdadsp [sof-hda-dsp], device 4: HDMI2 (*) [] Subdevices: 1/1 Subdevice card 0: sofhdadsp [sof-hda-dsp], device 5: HDMI3 (*) [] Subdevices: 1/1 Subdevice card 0: sofhdadsp [sof-hda-dsp], device 31: HDA Analog Deep Buffer (*) [] Subdevices: 1/1 Subdevice sudo aplay /usr/share/sounds/alsa/* sudo speaker-test --channels 2 --test wav --device hw:0,3
如果通过测试,参考这篇文章 安装 pipewire(ubuntu 22.04 之后逐步替代 pulseaudio)。注意,pipewire-pulse 可以不装,这个是为了兼容不支持 pipewire 的旧 app,所以:
sudo add-apt-repository ppa:pipewire-debian/pipewire-upstream sudo apt install pipewire pipewire-audio-client-libraries gstreamer1.0-pipewire libspa-0.2-bluetooth libspa-0.2-jack systemctl --user enable pipewire.socket systemctl --user start pipewire.socket
注意,这里不可以使用 root 身份执行。
standalone 模式 参考graysky2/kodi-standalone-service 进行设置,需要根据实际情况修改几处:
x86/init/kodi-gbm.service
14 行的 ExecStart 修改为正确路径,比如/usr/local/bin/kodi-standalone
x86/init/sysusers.conf
取消第 22、26 行注释,增加第 9 行注释,增加m kodi input
和m kodi plugdev
arctic fuse 2 皮肤
jurialmunkey 大神的经典 kodi 皮肤,前代已经不更新。安装之前记得先到插件设置里开启第三方插件的安装和更新权限,否则无法安装 TheMovieDB Helper 依赖,方法如下
具体安装步骤就不说了,网上一搜一大把,建议通过 repository 源安装。
最新版(v2.4.21)已经支持中文。但!是!不出意外的话少数汉字会变成方块,比如下图里的“管”、“年”
主要原因是 kodi 自带的 CJK 字体存在缺字漏字,可以看这个 issue 里的讨论。
解决办法当然是换字体,参考官方文档 。
以霞骛文楷为例,去 lxgw 拉取最新的 TTF 文件,为了防止超出 kodi 对外挂字体的限制选择 lite 版,应付日常足够了。记得把 Light、Medium、Regular 都下载下来,保存到 .kodi/media/fonts
(全局)或.kodi\addons\skin.arctic.fuse.2\fonts
(仅本皮肤生效)下面。.kodi 文件夹一般在用户根目录下,比如/storage/.kodi
,找不到的话可以查官方文档 。
然后修改.kodi/addons/skin.arctic.fuse.2/1080i/Font.xml
,在</fonts>
标签上面添加几行:
<fontset id ="LxgwWenkaiGB-Lite" unicode ="true" > <include content ="Font_Default" > <param name ="font_black" > LXGWWenKaiGBLite-Medium.ttf</param > <param name ="font_bold" > LXGWWenKaiGBLite-Medium.ttf</param > <param name ="font_regular" > LXGWWenKaiGBLite-Regular.ttf</param > <param name ="font_light" > LXGWWenKaiGBLite-Light.ttf</param > </include > </fontset >
id
的值就是字体显示的名称,然后在设置中修改皮肤字体,如下图
字体问题是解决了,但是跟 jellyfin 插件还是存在一些兼容性问题,比如无法显示文件详细资料
问题不大,后面再慢慢改进吧。