首图是目前的家庭网络设备拓扑图。虽然是教程贴,还是记录下前因后果。
看片困境 前段时间买了低功耗小主机,关掉了威联通 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 插件还是存在一些兼容性问题,比如无法显示文件详细资料
问题不大,后面再慢慢改进吧。