1. 先搞清楚 TVHeadend 到底是个什么东西
1.1 它解决的是"信号统一入口"的问题
TVHeadend 这个软件在圈子里被讨论得不少,但真正常年挂着跑的人不算多。它本质上是一个电视信号接收、整理、分发与录制的中枢:一头接信号源,另一头接客户端。信号源可以是插在机器上的 DVB 地面波/有线/卫星调谐器,也可以是自建的 M3U 播放列表;客户端则是 Kodi、VLC、手机播放器、浏览器,甚至是另一台电视盒子。
它的价值在于把"多路信号、多套节目单、多个客户端"这几件麻烦事收敛到一处。没有它,你得为每个信号源单独装播放软件,节目单各看各的,录制要开着一台机器专门盯着,想看回放还得自己去翻文件。有了它,频道列表是统一的,EPG 电子节目单是合并好的,录制是后台自动跑的,多个客户端还能同时拉同一路流而不用重复占用调谐器。
我最初接触它是因为手上有一堆零散的信号源和一个 USB 电视棒,想在局域网里给几台设备共用。当时试过直接把电视棒插在客厅的盒子上,结果只能那一台看,录制也别想。换成 TVHeadend 之后,信号集中在一台常年开机的小主机上,其他设备都变成"瘦客户端",体验一下子清爽了。这篇内容就是围绕docker搭建Linuxserver/tvheadend这条路径,把我从零到跑通、再到长期维护的完整过程拆开讲,包括容器参数怎么定、后台怎么配、哪些坑是必然会踩的。
适合谁看:手上有一台常年开机的小主机或 NAS、有合法的信号来源(自建天线接收的无线台、自有的播放列表、自己收藏的媒体流),想在局域网内统一管理频道与录制的朋友。完全没接触过容器的也能跟下来,但需要你会用 SSH,能看懂基本的命令行输出。
1.2 为什么我坚持用容器跑,而不是装宿主机
最直接的理由是依赖隔离。TVHeadend 本身不大,但它依赖一堆多媒体库、FFmpeg、XMLTV 抓取工具,还有各种编译期的编解码支持。裸装到宿主机上,版本一升级就可能把系统的多媒体库搅乱,尤其是那台机器上还跑着别的服务的时候。容器把这一整套依赖封在镜像里,升级就是换个镜像,回滚就是换回上一个 tag,心理负担小很多。
第二个理由是配置集中。TVHeadend 的所有状态都写在它的配置目录里:频道数据库、服务列表、录制规则、访问控制、EPG 缓存,全在一个文件夹下。容器化之后,这个目录通过卷映射到宿主机,备份就是打包一个文件夹,迁移就是把这个文件夹拷到新机器上再起一个容器。我用这个方式平移过两次硬件,每次都是十分钟内恢复,频道和录制规则一条没丢。
第三个理由是设备直通反而更清晰。很多人以为容器碰硬件很麻烦,实际上 Docker 的--device参数把"这个容器能用哪个设备节点"这件事表达得非常明确。/dev/dvb给调谐器,/dev/dri给核显做硬件转码,写清楚了就永远不会出现"某个服务偷偷占用了电视棒"的情况。
至于为什么选 LinuxServer 的镜像而不是官方镜像或者自己构建:LinuxServer 那一套镜像的约定非常统一,PUID/PGID 控制文件属主、TZ 控制时区、/config放配置,学一次就能套用到他们所有的镜像上。而且它预置了 FFmpeg 和常用的抓取工具,省掉了我自己写 Dockerfile 的功夫。缺点是镜像体积不算小,冷启动稍慢,但这是一次性成本。
1.3 这个镜像的几条"约定"必须先记住
在动手之前,有几条约定得先刻在脑子里,否则后面排查问题会绕远路。
第一,容器内部默认以abc这个用户运行,而它对应的宿主机 UID/GID 由PUID和PGID两个环境变量决定。这意味着你在宿主机上准备好的配置目录和录制目录,属主必须和这两个值一致,否则容器里写不进去。我见过太多人卡在这一步,日志里全是Permission denied,然后怀疑是镜像坏了。
第二,配置目录固定是/config,录制目录建议固定为/recordings。前者是必须映射的,后者看你是否要录制。这两个路径在镜像里是硬编码的,别自己改名。
第三,Web 管理端口是9981,HTSP 协议端口是9982。前者给你用浏览器进后台,后者给 Kodi 这类支持 HTSP 的客户端拉流。两个都要映射,只映射 9981 的话客户端连不上。
第四,镜像启动脚本会做一些初始化动作,比如修正目录权限、准备默认配置。所以第一次启动会比后续慢,日志里出现几行看起来像报错的东西也别慌,先等它跑完再看。
注意:这台机器的时间必须准。TVHeadend 的节目单、录制计划、时间平移全都依赖系统时间,时区配错了,EPG 会整体偏移几个小时,录制会在错误的时刻启动。
TZ环境变量一定要填对。
2. 部署前的环境盘点与准备工作
2.1 宿主机需要满足的硬性条件
硬件这块不需要多豪华,但有几点是硬门槛。
处理器方面,如果只做转发和录制(不转码),一颗低功耗的双核就够,我实测在一台四核低功耗小主机上,空闲时 CPU 占用长期在 3% 以下,四路同时录制也就跑到 20% 出头。但如果要开实时转码,尤其是把高清信号转成 H.264 给手机看,那就得看核显了,纯软件转码一路 1080i 就能吃掉两三个核。
内存方面,官方建议不大,但实际使用中 EPG 缓存和频道数据库会随着时间增长。我给它的起步配置是 2GB,跑了两百多个频道、七天的节目单,加上时间平移的缓冲,内存常驻在 600MB 到 900MB 之间。如果你打算开时间平移,建议给到 4GB。
存储方面要分成两块看。配置目录很小,几百 MB 顶天了;录制目录才是无底洞。一小时的 1080i 原始流大约 4 到 8GB,取决于码率。所以录制盘建议单独挂一块,别和系统盘混在一起,否则录满之后系统会跟着趴窝。
系统方面,主流的 Linux 发行版都行。内核版本建议不低于 5.4,因为较新的 DVB 驱动和核显驱动都在这个版本之后才稳定。还要确认一件事:你打算用的调谐器在宿主机上能不能被识别。先在宿主机上跑ls /dev/dvb,如果能看到adapter0之类的目录,说明驱动通了;看不到的话,容器里再怎么配都是白搭,得先把驱动搞定。
2.2 设备直通:调谐器与核显这两条线
设备直通是这个部署里最容易出问题的环节,我把它拆成两条线说。
第一条线是调谐器。USB 或 PCIe 的 DVB 设备在 Linux 下会暴露成/dev/dvb/adapterN系列节点,每个 adapter 下面有demux0、dvr0、frontend0等。容器要访问它们,最省事的写法是整目录映射:
--device=/dev/dvb:/dev/dvb这样容器里就能看到全部适配器。如果你有多个调谐器,只想给容器其中几个,也可以单独映射/dev/dvb/adapter0,但意义不大,因为 TVHeadend 内部还要按 adapter 编号去配置,映射不全会让编号对不上,反而更乱。
权限上还有个细节:/dev/dvb下的节点通常属于video组,权限是660。如果你的PGID不是video组的 GID,容器内可能读不到。解决办法有两个,要么把PGID设成 video 组的 GID,要么额外加一条--group-add把 video 组带进容器。我一般用后者,因为PGID还要管文件属主,不该为了设备权限去改它。
第二条线是核显。要用硬件转码就得把/dev/dri映射进去:
--device=/dev/dri:/dev/dri映射完还要确认容器内的用户有权限访问renderD128,这个节点一般属于render组。同样是--group-add解决。另外镜像里得有 VAAPI 相关的驱动库,LinuxServer 的镜像自带了一部分,但具体能不能用还得看宿主机内核和核显型号的匹配情况。我的建议是:先把纯软件跑通,确认整条链路没问题,再回头折腾硬件加速,不然同时引入两个变量,出问题根本分不清是谁的锅。
2.3 目录规划与 PUID/PGID 的确定
假设我打算把配置和录制都放在一个大盘上,规划成这样:
mkdir -p /srv/tvheadend/config mkdir -p /srv/tvheadend/recordings mkdir -p /srv/tvheadend/timeshift然后确定 PUID/PGID。先在宿主机上看当前用户的 ID:
id输出的uid=和gid=就是你要填的值。如果你打算用专门的服务账号跑,就先建号再看。我用的是一个不带登录权限的系统账号,UID 是 1000,GID 是 1000,这样和常见的桌面用户对齐,以后拷文件到别的机器上不容易出岔子。
目录属主要跟着改:
chown -R 1000:1000 /srv/tvheadend这里有个容易忽略的点:/srv/tvheadend这个父目录本身的权限也要对。如果父目录是700且属主不是你填的那个用户,容器里的进程连进去都进不去。最稳妥的做法是把父目录设成755,子目录交给容器自己管理。
时间平移目录单独列出来,是因为它的写入量极大。时间平移的本质是持续把当前播放的流缓存下来,方便你往回拖。这个目录会不停写入又不停丢弃,对磁盘寿命不友好。如果条件允许,把它放到一块独立的 SSD 上,或者干脆放进 tmpfs(内存盘),代价是重启就丢缓存,但换来的是零磨损和更快的回拖响应。我用的是独立的 SSD 分区,容量给了 30GB,够缓存一两个小时的高清流。
3. 两套部署命令,从快速验证到长期运行
3.1 先用 docker run 把服务拉起来
正式上 compose 之前,我习惯先用一条docker run快速验证,确认镜像能拉、容器能起、后台能进。这一步的意义是排除掉最基础的环境问题,别把一堆问题堆到 compose 文件里去排查。
命令长这样:
docker run -d \ --name=tvheadend \ --restart=unless-stopped \ -e PUID=1000 \ -e PGID=1000 \ -e TZ=Asia/Shanghai \ -p 9981:9981 \ -p 9982:9982 \ -v /srv/tvheadend/config:/config \ -v /srv/tvheadend/recordings:/recordings \ -v /srv/tvheadend/timeshift:/timeshift \ --device=/dev/dvb:/dev/dvb \ --device=/dev/dri:/dev/dri \ --group-add video \ --group-add render \ lscr.io/linuxserver/tvheadend:latest逐段解释一下我为什么这么写。
--restart=unless-stopped是必须的。这类常驻服务最怕的是机器重启之后忘了手动拉起来,等到想看节目的时候才发现服务没跑。用这个策略,只要你不是主动停的,重启后会自动恢复。
-p两条端口的映射,前面说过,9981 是后台,9982 是给客户端用的 HTSP。这里我用的是桥接网络加端口映射。如果你的信号源里有组播或者需要设备发现(比如 SAT>IP 场景),桥接模式下可能会因为组播跨不过网络边界而收不到流,这时候要改成--network=host,同时把-p参数去掉。代价是端口直接占用宿主机,得保证 9981/9982 没被别的服务占了。
--device两条是设备直通,--group-add两条是补权限。video组管 DVB 设备,render组管核显节点。这两个组的 GID 在不同发行版上可能不一样,但 Docker 支持用名字指定,会自动去宿主机解析,省去查 GID 的麻烦。
镜像地址我用了lscr.io这个前缀,它是 LinuxServer 官方推荐的镜像仓库入口,和ghcr.io/linuxserver/tvheadend是同一个东西。用哪个都行,我个人偏好前者,因为它对国内网络更友好一点。tag 用latest是为了省事,长期跑的话建议锁一个具体版本,理由后面讲升级的部分再说。
跑完之后看日志:
docker logs -f tvheadend正常情况下你会看到启动脚本先修一遍权限,然后 TVHeadend 开始监听端口。如果日志里反复出现Permission denied指向/dev/dvb,那就是权限没给够,回去检查--group-add和宿主机上设备节点的权限。
3.2 换成 compose,把参数固化下来
docker run验证通过之后,我会立刻转成 compose。原因很简单:这条命令太长了,改一个参数就得重打一遍,还容易漏。写成文件之后,参数是自解释的,改起来也安全。
对应的docker-compose.yml:
services: tvheadend: image: lscr.io/linuxserver/tvheadend:latest container_name: tvheadend restart: unless-stopped environment: - PUID=1000 - PGID=1000 - TZ=Asia/Shanghai - RUN_OPTS=--http_port 9981 --htsp_port 9982 ports: - "9981:9981" - "9982:9982" volumes: - ./config:/config - ./recordings:/recordings - ./timeshift:/timeshift devices: - /dev/dvb:/dev/dvb - /dev/dri:/dev/dri group_add: - video - renderRUN_OPTS这个环境变量是我加上去的,它的作用是把额外参数传给 TVHeadend 主进程。默认情况下镜像已经把端口配好了,写这一行主要是为了显式声明,同时给自己留一个"以后想加别的启动参数"的入口,比如调整日志级别、指定缓存目录之类。
devices和group_add在 compose 里都有对应的键,写法比命令行直观。这里要注意 YAML 的缩进,group_add是个列表,每一项单独一行,写成一行逗号分隔是不行的。
目录我用了相对路径./config,这样整个 compose 项目可以整体搬走。实际放置位置是/srv/tvheadend/docker-compose.yml,起停都在这个目录下执行:
docker compose up -d docker compose logs -f docker compose down提示:compose 文件的版本号在新版 Docker Compose 里已经不需要写了,
services直接顶格写就行。写了version:反而会有个警告。
3.3 第一次进后台要做的三件事
容器跑起来之后,浏览器打开http://你的机器IP:9981,会看到 TVHeadend 的界面。新版本首次访问会引导你创建管理员账号,如果没弹向导,说明配置目录里已经有旧配置了,先把容器停了把config目录清空再起。
进去之后先做三件事,顺序别乱。
第一,确认版本和运行状态。右上角能看到版本号,左侧菜单底部有个"关于"能看到运行时长和当前连接数。如果这里的时间明显不对,说明TZ没生效,回去改环境变量重启。
第二,配访问控制。默认情况下如果没设密码,局域网内任何人都能进来,这在家庭环境里勉强能接受,但如果你要把它暴露到更广的网络里,就必须在"配置 → 用户 → 访问条目"里把认证方式改成"明文密码"或"摘要",并给管理员账号设一个强密码。同一个页面里还能限制允许访问的网段,我一般只放行局域网段,其余全部拒绝。
第三,确认存储路径可写。进"配置 → 录制",看默认录制路径是不是/recordings,然后随便建一条测试录制规则,手动触发一下,看目录里有没有文件生成。这一步能提前暴露权限问题,比等到真正录节目的时候才发现强得多。我第一回就是跳过了这一步,结果一周后发现所有录制任务全部失败,原因就是录制目录属主填错了,白白丢了一周的节目。
4. 后台配置:从零到能看能录的完整链路
4.1 网络配置与访问入口的几个开关
进"配置 → 网络",这里有几个开关值得说清楚。
第一个是 HTTP 和 HTTPS 端口。默认 9981 同时承担 HTTP 和 HTTPS,如果你启用了 SSL,它会自动把 HTTP 关掉,只留 HTTPS。这个行为经常让人误以为服务挂了。我的做法是保持 HTTP 不启用 SSL,把 HTTPS 交给前面的反向代理去做,因为 TVHeadend 自带的证书管理比较简陋,换证书要重启服务。
第二个是"允许 HTTP 上的明文认证"。如果前面没有反向代理,局域网内直接访问,这个得勾上,否则某些客户端连不上。反之如果已经上了反向代理并强制 HTTPS,这个开关关掉更安全。
第三个是"流媒体端口"和"客户端流媒体端口"。这两个决定了实际推流的端口范围。默认是从 9981 出发,实际推流走的是 9983 往后的动态端口。如果你的机器上有防火墙,光开 9981、9982 是不够的,播放的时候会卡在"连接中"。要么把这一段端口全放行,要么在配置里把流媒体端口范围收窄到一个固定区间,只放行那个区间。我选的是后者,把范围设成 9983 到 9990,八个端口够用了,防火墙规则也清楚。
还有一处容易忽略:"高级"标签页里的"最大带宽"和"并发连接数"。默认值比较保守,如果家里同时有多个客户端在拉流,会看到播放卡顿但 CPU 和磁盘都不忙,那就是被这个限制卡住了。我把它按实际带宽的一半设,留出余量。
4.2 调谐器的添加与频道扫描
这是整个配置流程里最考验耐心的一步。
进"配置 → DVB 输入 → 网络",点"添加",你会看到三种类型:DVB-T(地面无线)、DVB-C(有线)、DVB-S(卫星),以及它们的变体。选哪个取决于你的调谐器支持哪种制式,也取决于你有哪种合法的信号来源。如果插的是 USB 电视棒,列表里会显示它能识别的制式,选匹配的那个就行。
添加时有个关键字段叫"预定义的复用器",它是一个频率表预设。选错了会导致扫描时间极长却什么都扫不到。原则是:这个预设要和你所在区域的广播发射参数匹配。如果你的调谐器厂商提供了频率表文件,也可以手动导入,比预设更准。
预设选好之后点"保存",然后回到网络列表,勾选刚建的那条,点"扫描"。扫描过程中可以在"服务"页面看到陆续出现的频道条目。地面波的完整扫描可能要十几分钟,卫星更长。我一般会盯着"复用器"那个页面看进度,如果某个频率反复超时,说明那个频率在你的区域根本不存在,直接把它从列表里删掉,能大幅缩短扫描时间。
注意:扫描期间不要反复停容器。TVHeadend 的扫描状态是持久化在配置库里的,中断再恢复可能留下半截数据,导致服务列表里出现重复条目。
扫描完成后,进"配置 → DVB 输入 → 服务",你会看到一大堆服务条目,很多是加密频道或者你根本不想要的。这里的操作是:先按名称或者加密状态筛选,把不要的批量删掉,剩下的再去做映射。别嫌麻烦,这一步做干净了,后面的频道列表才不会一团乱。
4.3 服务映射与频道整理
进"配置 → 频道/EPG → 频道",点"映射服务"。这个动作会把所有已扫描到的服务自动转成频道条目,并尝试按名称去重和合并。TVHeadend 的自动合并逻辑还算聪明,同一个频道在不同频率上重复出现的会自动归并,但也会误合并,比如两个不同地区同名的频道。
映射完成后,频道列表里会有一堆编号和名称。这时候要花点时间整理:把不要的删掉、把顺序调成你习惯的、给常看的频道设个固定编号。编号这件事看似无关紧要,实际上直接影响客户端的体验——Kodi 里按数字直接跳台就靠这个编号。
我给自己的规则是:新闻类从 1 开始,综合类从 10 开始,体育从 20 开始,其余按发现顺序往后排。这样在遥控器上盲按也能大致定位。整理完记得点一次"保存",TVHeadend 的频道编辑器不会自动保存。
这里还有个细节:频道有个"启用"开关,关掉之后频道还在但不参与流媒体分发。我在做大规模调整的时候会先全部关掉,整理完再分批打开,避免客户端在此期间反复刷新到半成品列表。
4.4 EPG 电子节目单的三种接法
没有节目单的电视系统是没有灵魂的,因为你只能"现在有什么看什么",没法提前安排录制。TVHeadend 支持几种 EPG 来源,实际用下来各有取舍。
第一种是随信号下发的 EPG,也就是复用流里自带的节目单数据。这种方法零配置,在调谐器配置里勾上"使用 EPG"就行,扫出来的频道自动带节目单。缺点是覆盖率取决于发射端,而且每次刷新都要占用调谐器去接收,多个频点轮询下来会挺慢,一般要几个小时才能刷完整。
第二种是XMLTV 外部抓取。TVHeadend 内置了几个抓取器,在"配置 → 频道/EPG → EPG 抓取器"里能看到列表。选择匹配你频道来源的抓取器,设好抓取时间,它会定期把数据拉回来。需要说明的是,内置抓取器覆盖的范围有限,很多区域得靠外部的 XMLTV 文件。
第三种是外部 XMLTV 文件或 socket。你可以用任何工具生成一个标准 XMLTV 格式的文件,放在宿主机上,然后在 EPG 抓取器里选"XMLTV:从文件获取",填上容器内的路径。这个路径必须是容器内可见的,所以要在 compose 里多挂一个只读卷。我用的是这个方式,因为生成端可以完全自己控制,抓取频率、数据清洗、多源合并都能自己做主。
节目单的时区是最常见的坑。如果抓回来的节目单时间整体差了几个小时,先别怀疑抓取器,先检查三处:宿主机时区、容器TZ环境变量、EPG 数据里声明的时间偏移。这三者任何一个不一致,都会导致偏移。我在这个坑里待过一个下午,最后发现是抓取端输出的时间没带时区标记,被 TVHeadend 按本地时间解析了。
4.5 录制、时间平移与串流的策略配置
录制这块的配置项不少,但真正影响日常使用的就几个。
"录制路径"要填容器内的路径,也就是/recordings。下面有一堆命名模板,比如$c是频道名、$t是节目名、$s是开始时间。默认模板会把文件按频道分目录,我觉得挺好用,就没改。如果你的录制盘做了文件系统大小写敏感限制,注意模板里别加特殊字符。
"录制后处理"可以挂一个脚本,录完之后自动转码、压缩或者移动。这个功能很实用但也很容易搞坏东西,建议先手动测脚本,确认能跑通再接进去。脚本是运行在容器内的,容器里的工具链和宿主机不一样,别指望宿主机上的命令能直接用。
"时间平移"是另一个独立模块,在"配置 → 录制 → 时间平移"里开。开启后要指定缓冲目录和最大容量。这里我要强调一遍:时间平移的写入量非常大,如果缓冲目录和录制目录在同一个机械盘上,一边录一边平移,磁头来回寻道,两边都会卡。分开盘,或者至少分到不同的物理设备上。
"串流配置"在"配置 → 串流 → 串流配置"里。默认有几个预设,pass是不转码直接透传,webtv-h264-aac是转成 H.264 加 AAC 给浏览器用,matroska是封装成 MKV。我的用法是:局域网内的 Kodi 客户端一律走pass,因为它能直接解原始流,不转码对 CPU 最友好;手机和浏览器走转码配置,因为移动端解码能力有限。
如果要用硬件转码,在转码配置里把编码器设成vaapi或对应的硬件接口,并确认/dev/dri已映射且权限到位。转码参数里的码率和分辨率要按实际网络情况设,别一上来就设 8Mbps,手机在弱网下会一直缓冲。
5. 故障排查实录:那些我真实踩过的坑
5.1 容器启动类问题速查
这一类问题的表现是"容器起来了但后台打不开"或者"容器一直在重启"。
先看日志,docker logs tvheadend的输出是第一手信息。如果最后几行是Permission denied且路径指向/config,那是 PUID/PGID 和目录属主不一致。修复方式是chown -R到正确的 UID/GID,然后重启容器。这个问题我在第一次部署时遇到过,原因是宿主机上的目录是用 root 建的,属主是root:root,而容器里跑的是 1000。
如果日志显示端口被占用,那是宿主机上已有别的进程占了 9981 或 9982。用ss -tlnp | grep 9981查一下是谁,能停就停,不能停就改映射端口。改端口之后记得同步改反向代理的配置,不然还是打不开。
如果容器反复重启,日志里出现 "database is locked",说明上一轮进程没有正常退出,SQLite 的锁文件还留着。停掉容器,把/config下的*.lock文件删掉,再起。这个问题的诱因通常是宿主机强制关机或者docker kill,所以我现在都习惯用docker compose down停服务,让进程有机会正常收尾。
还有一种情况是镜像拉取失败,日志里什么都看不到。这时候先单独docker pull一下镜像,确认网络和仓库访问没问题。镜像仓库的访问有时会不稳定,重试几次或者换个时间段就行。
| 现象 | 大概率原因 | 处理方式 |
|---|---|---|
| 后台打不开,容器在跑 | 端口映射错误或被占用 | 检查-p与宿主机监听 |
日志报/config权限拒绝 | PUID/PGID 与目录属主不符 | chown -R后重启 |
| 反复重启,日志有锁错误 | 上次未正常退出 | 删除*.lock后重启 |
| 镜像拉不下来 | 仓库访问不稳定 | 单独docker pull重试 |
| 设备节点看不到 | 未直通或驱动未加载 | 检查--device与宿主机/dev/dvb |
5.2 扫描与信号类问题
"扫不到台"是这个系统里最让人挫败的问题,因为它涉及硬件、驱动、参数三层,任何一层出错表现都一样。
先把问题分层。第一层,宿主机上能不能看到设备。ls -l /dev/dvb/adapter0看看节点在不在,dmesg | grep -i dvb看看驱动有没有加载成功。如果宿主机这一层就不通,容器里怎么调都没用,得先解决驱动问题。第二层,容器里能不能看到设备。docker exec -it tvheadend ls /dev/dvb确认直通生效。第三层,TVHeadend 的界面里能不能看到适配器。在"配置 → DVB 输入 → 适配器"里应该能看到,看不到说明权限不足或者设备被别的进程占着。
三层都通了之后还是扫不到台,那就去查参数。最常见的错误是频率表预设选错了,选了一个和实际信号完全不匹配的预设,扫一整天也是零。其次是天线问题,接头没拧紧、线材老化、天线方向偏了,都会导致信号强度不够。TVHeadend 在扫描时能显示每个频率的信号强度和误码率,盯着那个数值看,如果全是零或者误码率极高,那就是物理层的问题,别再折腾软件了。
还有一种情况是"扫到了但播放无信号"。这多半是服务映射之后,频道指向的那个服务实际不可用,比如加密频道或者信号间歇性中断。在"服务"页面看对应条目的状态,能直接看出问题。
5.3 播放与转码类问题
播放卡顿的原因可以分成"网络""磁盘""CPU"三类,排查思路是从最容易观测的开始。
先看是不是所有客户端都卡。只有一台卡,那问题在客户端或者它和服务器之间的网络;所有客户端都卡,问题在服务端。服务端的话,docker stats看 CPU 和内存占用,iostat看磁盘 IO。如果 CPU 跑满,那是转码扛不住,换成pass透传,或者降低转码的分辨率码率,或者上硬件加速。如果磁盘 IO 打满,那是时间平移在疯狂写盘,把缓冲目录挪走。
"能播但没声音"这种情况,多半是音频编码不被客户端支持。原始流里的音频可能是 AC3 或者 E-AC3,浏览器和部分手机解不了。解决方式是在串流配置里加一条音频转码规则,把音频转成 AAC,视频保持透传。这样只转音频,CPU 开销很小。
"播放几秒后断开"是另一个典型症状。如果日志里能看到连接被主动关闭,检查一下"最大带宽"那个限制,或者访问控制里有没有设会话超时。我给一个客户排查过一次,最后发现是他路由器上的空闲连接回收策略太激进,把长连接掐了,改成保活之后就正常了。
转码画质差是参数问题。默认的转码预设追求低带宽,画质比较将就。想改善的话,在转码配置里提高码率、把预设从veryfast改成faster,代价是 CPU 占用上升。我的经验是,1080i 转 1080p 用faster预设,码率给到 6Mbps,画质在手机上看已经很不错了。
5.4 录制与存储类问题
录制失败的表现很统一:任务列表里显示已调度,到点之后状态变成失败或者干脆没动静。
第一个要查的是路径。容器内的/recordings是不是真的可写,用docker exec进去touch一个文件试试。这一步能排除掉大部分问题,因为权限问题占了录制失败原因的一大半。
第二个要查的是磁盘空间。TVHeadend 有个"最小空闲空间"的阈值,低于这个值就拒绝开始录制,防止把盘写满导致系统崩溃。默认值设得比较保守,如果你的录制盘很大,可以在配置里调高;如果盘不大,就调低一点,但要确保留出安全余量。我见过有人把这个值设成 0,结果录到盘满,SQLite 写不进去,整个数据库都损坏了,配置全丢。
第三个要查的是信号。如果录制期间信号中断,录制文件会是残缺的,TVHeadend 会把它标记为失败。这种情况没法完全避免,但可以在录制配置里开启"部分录制保留",这样即使中途断流,已录的部分也能留住。
第四个是文件名问题。如果命名模板里包含了某些文件系统不支持的字符,或者路径太长超过了限制,录制会静默失败。我用的模板很简单,只有频道名加日期时间,从来没出过问题。
注意:不要在录制进行中手动改配置目录里的文件。TVHeadend 的配置是内存加载加定时写盘的,手动改文件会被覆盖,还可能造成数据不一致。要改就停下来改。
6. 长期维护:备份、升级和几条血泪经验
6.1 备份、升级与回滚的稳妥姿势
TVHeadend 的所有状态都在/config里,所以备份极其简单:
docker compose stop tar -czf tvheadend-config-$(date +%F).tar.gz ./config docker compose start先停再打包是有必要的,因为 SQLite 数据库在运行中会有 WAL 文件和未落盘的页,直接拷可能拿到不一致的快照。停服务只花几秒钟,这点代价值得付。
配置里值得单独备份的是"访问控制"和"录制规则",这两个重建起来最费时间。频道列表反而好办,重新扫一遍就有。
升级策略上,我不建议长期跟latest。TVHeadend 的版本迭代里偶尔会带数据库结构变更,新版本升级之后旧版本可能打不开配置库,想回滚都回不去。我的做法是锁定一个具体版本号,比如某个确定稳定的 tag,跑几个月没问题之后,再评估要不要跟进。真要升级,流程是:先备份配置,改 compose 里的 tag,docker compose pull,docker compose up -d,进去看日志有没有迁移错误,验证几个频道能播、一条录制能跑,再观察一两天。有问题就把 tag 改回去,用备份的配置恢复。
还有一个细节:升级前把旧的镜像留着别删。docker image prune平时可以跑,但升级前后别跑,留着旧镜像才能在几分钟内回滚。磁盘空间紧张的话,至少留一个旧版本。
6.2 资源占用、性能调优与容量规划
长期跑下来,我总结出几个影响性能的关键点。
关于频道数量,TVHeadend 的启动时间和频道数量、EPG 数据量强相关。我实测过,一百来个频道的时候启动是秒级,堆到五百多个频道加七天节目单,启动要十几秒,界面首次加载也变慢。所以别贪心把扫到的频道全留下,删掉不看的能明显改善体验。
关于 EPG 抓取频率,抓得越勤数据越新,但每次抓取都是一次全量或者增量写入。我设的是每天凌晨抓一次,足够用了。如果你需要更及时的更新,改成每六小时一次也能接受,但别设成每小时,数据库会一直处于写入状态。
关于并发,一个调谐器同一时间只能服务一路信号。这意味着两个人同时看不同频道的节目,就需要两个调谐器或者两个适配器。如果你只有一个调谐器,又想让多台设备同时看,要么接受"看同一个台",要么在串流配置里做限制。TVHeadend 有个"订阅权重"的机制,可以给不同客户端设优先级,高优先级的能抢占低优先级的资源,适合家里有"主电视"和"手机随便看看"这种场景。
关于内存,它的占用主要跟 EPG 缓存和连接数有关。如果发现内存持续增长不回落,先看是不是 EPG 数据里混进了异常大的条目,其次看有没有客户端连接泄漏。我遇到过一回,某个客户端反复重连但不释放会话,两天时间把内存吃到了 1.5GB,重启服务就恢复了。后来我在访问控制里把这类客户端的会话超时调短了。
6.3 几条踩过坑之后才明白的经验
第一条,先用最简单的配置跑通,再逐项加功能。我一开始就想一步到位,DVB 调谐器、IPTV 源、硬件转码、时间平移全开,结果出问题的时候完全不知道是哪一项引起的。后来全部推倒,先只留一个调谐器、一路透传播放,确认通了之后,一次只加一个功能,每次加完都验证一遍。这个笨办法救了我很多时间。
第二条,别把"能播"当成"配好了"。能播只是最低标准,还要验证:切台快不快、EPG 显示对不对、录制能不能正常开始和结束、多个客户端同时拉流会不会互相影响。这些都要在正式投入使用之前跑一遍。我第一版上线之后就发现切台要等十几秒,原因是转码预设太重,换成透传之后变成一秒内。
第三条,设备的权限问题会以各种奇怪的形式出现。同样是"权限不足",有时候表现为扫不到台,有时候表现为录制失败,有时候表现为转码回退到软件。遇到这类"看起来不像权限问题"的症状,先花一分钟检查一下相关目录和设备节点的属主与权限,往往能直接命中。
第四条,日志要看,但别只看最后几行。TVHeadend 的日志里,真正的问题描述经常夹在一堆正常输出中间。我现在的习惯是docker logs tvheadend 2>&1 | grep -iE "error|fail|denied|timeout",把可疑的行筛出来看,比从头翻到尾高效得多。
第五条,给录制留出足够的余量。一小时的节目因为信号波动多录了十分钟,或者因为时间偏移少录了几分钟,都会让人抓狂。我的做法是在录制规则里加前后各两分钟的提前和延后,同时把磁盘的保留空间设成至少 10%。这两条设置救过我好几次,尤其是跨天的节目和直播类的节目。
第六条,时间平移是个"看起来很美"的功能,但它对硬件的要求被严重低估了。刚开始我把它和时间平移放在同一块机械盘上,结果一到晚上录制高峰,播放就开始卡,因为一块盘同时承担持续写入和随机读取。后来把时间平移挪到 SSD 上,问题立刻消失。如果你打算开这个功能,先确认你有独立的存储设备给它,否则不如不开。
第七条,客户端选择会影响你对服务端的判断。同一个频道,用 Kodi 的 PVR 客户端播放很流畅,换成浏览器就卡,这不一定是服务端的问题,可能是浏览器的解码能力或者转码配置不匹配。排查的时候固定用一个客户端做基准,别在多个客户端之间来回对比,很容易得出错误结论。
最后提一个容易被忽略的点:这台机器的散热。常驻跑转码的时候 CPU 会长时间维持在中高负载,如果机箱散热不好,夏天会降频甚至死机。我在机箱上加了一个低速风扇,把 CPU 温度压到了 60 度以下,连续跑了一个夏天没出过问题。这种"非技术"的因素,往往才是长期稳定运行的关键。