HDMI-CEC

来自 ArchWiki

高清晰度多媒体接口 - 消费电子控制 是 HDMI 连接中的一个额外的低速(50 B/s)总线,HDMI 设备“网络”可以使用它来相互通信。它允许 HDMI 设备相互通知它们应该开启或关闭,电视已切换输入或正在按下遥控器按钮等。在 PC 设置中,它通常在 HTPC(家庭影院 PC)设置中遇到。

由于各种原因,几乎没有 PC GPU 具有对 CEC 的硬件支持。视频游戏机和机顶盒通常必须包含外部芯片组来驱动 CEC 引脚。虽然有些设备具有原生 CEC 支持(例如 Raspberry Pi 上的 VideoCore GPU),但大多数硬件配置都需要额外的硬件。

特性

CEC 的主要目的是让电视能够洞察和控制插入其中的设备的状态。因此,它分为十几个“特性”,每个特性都针对特定的用例,设备可以根据其作为发起者/跟随者的角色、其功能以及用户配置来选择支持或不支持。

标准化的特性有

一键播放
让设备发出信号,表明它希望立即成为活动源,这可以自动打开电视
路由控制
允许电视控制 HDMI 切换器,并让设备检查当前活动的源是什么
遥控器信号传递
让设备相互发送遥控器信号,通常是从电视到活动源
甲板控制
用于控制电影/音乐播放器并查询其播放状态
待机/系统待机
让设备请求关闭另一个特定设备,或广播系统上的所有设备现在都应关闭。
电源状态
让设备被探测以查看它们是处于待机模式还是已开启,或者它们是否正在开启过程中。
系统音频控制
允许控制通过电视音频回传通道连接的 AV 接收器,允许更改音量并打开或关闭接收器。
调谐器控制
让任何设备逐步浏览调谐器设备已知的电视频道列表,并查询有关活动频道的信息,例如模拟电视的频道号或数字电视的 DVB/ATSC/ARIB 传输流信息
一键录制
使录像机能够查询电视当前正在播放的频道,以便该录像机可以调谐到同一频道并开始录制,或者知道如果它已经是当前活动的 HDMI 源,则应录制自身或下游设备
定时器编程
允许电视在录像机上配置定时器,以在特定时间开始录制给定的源
OSD 显示
允许设备在电视上打印消息,长度在 1 到 13 个 ASCII 字符之间
动态自动唇音同步
允许电视向音频接收器广播演示延迟的变化,具有自身扬声器的源(如 PC)可以使用该接收器进行图像的延迟补偿
提示: 未列出:设备菜单控制(在 CEC 2.0 中已弃用)、OSD 名称传输(由 cec-ctl 处理)、系统信息、供应商特定命令(无标准化消息)、音频速率控制(仅限 TV-AV)、音频回传通道控制(仅限 TV-AV)

对于像 PC 这样的设备,这些特性中最有用的是 遥控器信号传递系统待机 可能对 HTPC 有用,但在更通用的机器上用途值得怀疑,这些机器通常不希望在屏幕关闭时进入睡眠状态。路由控制 可用于在电视尝试显示该输入时唤醒系统,前提是连接的 PC 有办法在暂停时监听 CEC 流量。系统音频控制 对于某些 HDMI 声音输出可能很方便,但目前不能用作 PipeWirePulseAudio 的音量控制方式。

硬件设置

Linux 内核 已经有一个内置子系统来自动响应查询和处理 CEC 事件,但可能需要先配置硬件才能工作。

原生 CEC

原生 CEC 主要在 ARM 设备中遇到。在 x86 世界中,最简单的选择是 通过 DisplayPort 隧道传输,否则只有一些 Chrome OS 设备和 SECO UDOO 单板计算机提供 CEC。

通过 DisplayPort 隧道传输

注意: 这已确认为至少适用于 i915nouveauamdgpu 驱动程序。

DisplayPort 1.3 标准(于 2014 年推出)允许 DisplayPort 到 HDMI 适配器使用辅助通道双向转发 CEC 信号。除非特别提及,否则这种特性通常不被适配器支持,并且不常见,但与 USB-CEC 适配器相比,通过 DisplayPort 进行 CEC 隧道传输可能反而更便宜且更易于使用。CEC 子模块的内核文档页面有一个 已确认可用的适配器列表

但是,该列表并不详尽,了解适配器使用的芯片组名称通常比适配器型号更有用。例如,Framework HDMI 模块卡未明确宣传为支持 HDMI-CEC,但其内部的 Parade PS186 芯片组 确实支持,并且该卡本身被 cec-ctl 检测到并按预期工作。另一方面,Synaptics “Spyder” VMM7100 被多个 DisplayPost-to-HDMI-2.1 适配器(如 Club3D CAC-1088)使用,仅进行信号处理,并且似乎不支持 CEC-over-AUX。这与 Club3D CAC-1080 不同,后者是一个非常相似的基于 Megachips MCDP2900 芯片组[死链 2024-12-15 ⓘ] 的 HDMI 2.0 适配器,它 *确实* 支持 CEC。

CEC 适配器

注意: 如果您希望将 HDMI-CEC 与 Kodi 一起使用,请注意它具有 对 Pulse-Eight 和 Raspberry Pi 模式的 CEC 控制的内置支持,并且与以下配置不兼容。

PulseEight USB 适配器

PulseEight USB-CEC 适配器 的工作原理是被动扩展从“PC 侧”连接器到“电视侧”连接器的 HDMI 连接器的所有引脚,除了 CEC 引脚,该引脚被拦截。通过该引脚的数据通过 USB 串行接口公开,以允许 PC 控制和监控 CEC 流量。在内核接管并将其确认为 CEC 适配器之前,串行设备 需要手动配置其线路规程(一个标志,用于向内核发出 TTY 是特定已知类型并需要驱动程序才能工作的信号)。由于 围绕串行设备 API 的限制,这无法自动完成,因此目前最好的方法是将 udev 规则systemd 单元 配对(因为 udev 规则无法启动长时间运行或派生进程)以在插入设备时运行 inputattach --pulse8-cec ...

此串行接口显示为设备节点 /dev/ttyACMX,并且需要 inputattach 实用程序来设置线路规程并让内核驱动程序接管以创建稍后需要的 /dev/cecX 设备。这需要 linuxconsole 软件包。

注意: 请勿修改以下规则中的 @$devnode,它是 udev 字符串替换。
/etc/udev/rules.d/pulse8-cec-autoattach.rules
SUBSYSTEM=="tty" ACTION=="add" ATTRS{manufacturer}=="Pulse-Eight" ATTRS{product}=="CEC Adapter" TAG+="systemd" ENV{SYSTEMD_WANTS}="pulse8-cec-attach@$devnode.service"
/etc/systemd/system/pulse8-cec-attach@.service
[Unit]
# Should be called as "pulse8-cec-attach@-dev-ttyACM0.service" or similar
Description=Configure USB Pulse-Eight serial device at %I
ConditionPathExists=%I

[Service]
Type=forking
# inputattach is built without systemd daemon support by default, so systemd will have to guess the PID.
# https://sourceforge.net/p/linuxconsole/code/ci/a3366c0d5f82485e6aae7b005ec7a2d9a93bf458/tree/utils/inputattach.c#l1233

ExecStart=/usr/bin/inputattach --daemon --pulse8-cec %I

但是,USB 设备连接 通常在系统从睡眠状态唤醒时重置一个称为 reset-resume 的步骤),这意味着如果计算机曾经被挂起,串行连接将会丢失,此外,串行连接通常也会在恢复时挂断。这意味着上述规则必须以某种方式再次触发。

不幸的是,负责 ttyACM* 对象的 cdc_acm 驱动程序(上述规则对其做出反应)不会引发任何关于连接重置和线路规程丢失的 uevent,并且该规则无法直接钩住 USB 设备。相反,使上述使用的规则在正确的时间再次触发的最可靠方法是删除并重新创建 ttyACM* 对象,方法是 强制 USB 设备在重置时重新配置。为了对此做出反应并确保重新打开连接,udev 可以跟踪 USB 设备何时重置和枚举,如 DEVNUM 属性被置零并在稍后恢复所证明的那样,并触摸 bConfigurationValue sysfs 属性。

/etc/udev/rules.d/pulse8-cec-autoattach.rules
SUBSYSTEM=="tty" ACTION=="add" ATTRS{manufacturer}=="Pulse-Eight" ATTRS{product}=="CEC Adapter" TAG+="systemd" ENV{SYSTEMD_WANTS}="pulse8-cec-attach@$devnode.service"

# Force device to be reconfigured when reset after suspend, otherwise the ttyACM link is lost but udev will not notice.
# A usb_dev_uevent with DEVNUM=000 is a sign that the device is being reset before enumeration.
# Re-configuring causes ttyACM to be removed and re-added instead.
SUBSYSTEM=="usb" ACTION=="change" ATTR{manufacturer}=="Pulse-Eight" ATTR{product}=="CEC Adapter" ENV{DEVNUM}=="000" ATTR{bConfigurationValue}=="1" ATTR{bConfigurationValue}="1"

这实际上就像 USB 适配器在从睡眠状态唤醒后立即被拔下并重新插入一样,确保之前的 SUBSYSTEM=="tty" ACTION=="add" 规则再次运行。这确保了 systemd 服务将在设备恢复可用状态后立即重新启动。

软件设置

现在 CEC 子系统已经有了一些可以绑定的东西,并且 /dev/cec0 已经创建,现在可以配置 PC,以便其他 CEC 设备了解它。使用命令行时,CEC 设备通常通过 cec-ctl 控制,它是 v4l-utils 的一部分。

告知 USB 适配器其物理地址

需要注意的一件事是,仅靠 CEC 引脚本身没有足够的信息来发送有效的 CEC 消息。仅监控引脚 13 (CEC) 的 CEC 适配器无法知道其“物理地址”,这是一个 16 位值,表示其在 HDMI 设备“树”中端口号方面的位置。

注意

电视是 HDMI 根,始终具有地址 0.0.0.0。直接插入电视端口 HDMI2 的设备将具有地址 2.0.0.0。插入 AV 接收器的端口 6 的设备(AV 接收器本身插入电视的 HDMI3 端口)将是 3.6.0.0

适配器需要知道此物理地址才能完成逻辑地址分配过程,这在下一节中详细介绍。物理地址通过引脚 16 (DDC/EDID) 通信,因此配置 CEC 子系统包括指定哪个显示输出端口应该与该 CEC 对象关联,以便从显示器的 EDID 中提取物理地址。

查找活动连接器名称的一种方法是使用 xrandr --query(也适用于 Wayland)

$ xrandr --query 
Screen 0: minimum 16 x 16, current 3840 x 2160, maximum 32767 x 32767
DP-1 connected primary 3840x2160+0+0 (normal left inverted right x axis y axis) 600mm x 340mm
   3840x2160     59.98*+
   2048x1536     59.95  
   ...
HDMI-A-1 connected 3840x2160+0+0 (normal left inverted right x axis y axis) 1440mm x 810mm
   3840x2160     59.98*+
   2048x1536     59.95  
   ...

一旦确定了正确的端口(例如 HDMI-A-1),则可以使用 ls -1d /sys/class/drm/card*-HDMI-A-1 找到 sysfs 端口名称(例如 card1-HDMI-A-1)。在这种情况下,相应的显示器的 EDID 数据将保存在 /sys/class/drm/card1-HDMI-A-1/edid 中。

物理地址可以像这样预览

$ edid-decode --physical-address /sys/class/drm/card1-DP-3/edid
4.0.0.0

鉴于每次重新创建 cec 设备节点时都必须执行 CEC 配置,因此最好使用另一个 udev 规则来处理,该规则在 cec 对象出现时触发。

注意: 请务必将以下规则中的 card1-HDMI-A-1 替换为您的连接器名称。
/etc/udev/rules.d/cec-configure-autostart.rules
SUBSYSTEM=="cec" KERNEL=="cec0" ACTION=="add" TAG+="systemd" ENV{SYSTEMD_WANTS}="cec0-configure@card1-HDMI-A-1.service"
/etc/systemd/system/cec0-configure@.service
[Unit]
# Should be called as "cec0-configure@card1-HDMI-A-1.service" or similar
Description=Configure CEC adapter cec0 assuming it runs on output %i
AssertPathExists=/sys/class/drm/%i/edid
BindsTo=dev-cec0.device

[Service]  
Type=exec  
# --phys-addr-from-edid-poll checks EDID every tenth of a second
# https://git.linuxtv.org/v4l-utils.git/tree/utils/cec-ctl/cec-ctl.cpp?id=0a195181d771090f3c99d4a6ddb8151352509061#n1977
# Use `Type=oneshot` if using `--phys-addr-from-edid` instead
ExecStart=/usr/bin/cec-ctl --device=0 "--osd-name=%H" --playback "--phys-addr-from-edid-poll=/sys/class/drm/%i/edid"

有关更多详细信息,请参阅下一节。

获取逻辑地址

由于带宽非常有限,CEC 协议使用较短的 4 位逻辑地址而不是 16 位物理地址来标记每条消息的来源和目的地。如果没有逻辑地址,设备只能接收和发送广播消息。这会将设备识别为“调谐器 #3”或“播放设备 #1”,每种类型的设备数量有限。这些角色旨在与前面提到的 CEC 特性 相关,即

调谐器(最多 4 个)
应支持“调谐器控制”特性。
录像机(最多 2 个)
唯一可以使用“一键录制”的类型;电视应该忽略来自其他地址的相关消息
播放设备(最多 3 个)
通用视频源。计算机和视频游戏机被认为是“播放设备”
备份(最多 2 个)
如果地址分配失败,因为存在太多一种类型的设备,则可以使用

其余 4 个地址保留给电视本身、音频系统、模糊的“特定用途设备”(可能是第二台电视)和默认广播/未注册地址。HDMI 切换器是“透明的”,没有自己的地址。

与上次不同,所需的 cec-ctl 调用足够短暂,可以直接作为 udev 规则的一部分工作。如果您之前已经为物理地址配置创建了 systemd 单元,则此规则将是多余的,可以忽略。

/etc/udev/rules.d/cec-configure-autostart.rules
SUBSYSTEM=="cec" KERNEL=="cec0" ACTION=="add" RUN+="/usr/bin/cec-ctl '--device=$devpath' '--osd-name=14_CHARS_MAX' --playback"

上述 udev 规则(和之前的 systemd 单元)使用 --playback 来配置播放设备。但是,通常可以接受将设备类别设置为调谐器 (--tuner) 或录像机 (--record),无论是由于没有更多未使用的播放地址,还是仅仅为了让 PC 在电视的输入菜单中突出显示,这些电视在视觉上区分了每个设备类别。该规则还使用 --osd-name=14_CHARS_MAX 来配置要在电视菜单中使用的广告源名称。顾名思义,它仅限于 14 个 ASCII 字符。

输入处理守护进程

用户空间工具

可以通过将用户添加到 video 用户组 来授予用户对 /dev/cec* 设备的访问权限。用于控制 CEC 设备的基本工具是来自 v4l-utilscec-ctl。来自 libceccec-client 是一个类似的工具,其中 python-cecAUR 中也提供了 Python 绑定。

CEC 的用途

遥控器信号传递

现在 cec0 对象已配置,Linux CEC 子系统也应该在 /sys/class/rc 中创建了一个匹配的对象,该对象充当输入设备,将 UI 命令代码信号转换为等效的按键

  • “电源”信号通常不在遥控器本身上,但可以通过菜单发送,默认情况下映射为在 KDE 中调出“关闭会话”确认屏幕,并在 GNOME 中立即挂起系统,就像键盘上的挂起按钮一样。
  • 箭头按钮映射到箭头导航键,将在任何地方工作。
  • “后退”和“选择”导航按钮未映射到“Escape”和“Enter”键,而是映射到“OK”和“EXIT”媒体按钮,这些按钮仅被某些媒体播放器识别。
  • “播放”、“暂停”和“停止”的工作方式与媒体键相同,并且在任何地方都可识别,甚至可能在播放器未聚焦时(具体取决于 DE 配置)。
    • “快退”、“快进”、“下一曲目”和“上一曲目”通常被忽略,但 VLC 和 Totem 会识别它们
  • 数字按钮群集中的“点”被识别为小键盘“.”键
  • 各种其他键(如“菜单”、“设置”、“音频”和“录制”)默认情况下不执行任何操作,但可以由大多数应用程序分配为有效的键盘快捷键。
  • 4 个彩色功能按钮、数字按钮和频道 +/- 按钮等,不会被应用程序识别为键盘或媒体键,尽管它们引发的 evdev 按键事件仍然可以被专门检查它们的应用程序使用。
  • 音量控制通常被电视拦截,而不是传递到源,但在传递的情况下,它被视为正常的音量键。

CEC 唤醒

HDMI 设备通常在它们成为活动源时由电视通知,使用路由控制功能。这可以用于唤醒挂起的设备,尽管在计算机方面会遇到问题。由于 CEC 支持是通过 Linux 内核或用户空间库实现的,而不是由主板本身处理的,因此在计算机挂起时,没有任何东西在监听 CEC 流量。

与 DisplayPort 到 HDMI 适配器相比,Pulse-Eight 适配器的一个优点是,即使其主机系统处于待机状态,它也可以保持供电和活动状态。一旦它被告知其物理地址和逻辑地址,如果它检测到其 USB 主机已消失,只要它保持供电,它将继续在“自主模式”下运行。在此状态下,它将继续执行以下操作

  • 回复系统信息查询(屏幕显示名称、逻辑地址、当前电源状态等)
  • 通过向 PC 发送电源切换事件来唤醒 PC,以响应任何“电源”遥控器信号或以自身为目的地的 <Set Stream Path> 消息

Pulse-Eight USB 适配器可以做到这一点,因为它将其自身注册为 USB 键盘及其串行接口。DisplayPost-to-HDMI 适配器(包括使用 DisplayPort 替代模式的 USB-C 适配器)没有这种侧通道,因此无法像这样唤醒主机。

本文或章节需要扩充。

原因: 根据我的测试,Pulse-Eight 适配器在主机挂起一段时间后可能会停止响应。原因仍然不清楚,应该澄清。(在 Talk:HDMI-CEC#Pulse-Eight:_Unreliable_wake-on-CEC 中讨论)

具有原生 CEC 支持的设备(如 Raspberry Pi)通常没有挂起模式。虽然 Pi 本身可以断电,但在此状态下它不会监听 CEC 引脚,并且 需要外部电路 以这种方式通电。

CEC 流量监控

可以使用支持 CEC 的设备完成的一件有趣的事情是,只需轻敲 CEC 线即可查看其他设备在说什么。由于 CEC 线在物理上在所有设备之间互连(甚至是不支持 CEC 的设备),因此任何设备发送的所有消息都对所有其他设备可见,无论它们的位置如何。为了使用 cec-ctl 记录这些消息,只需运行以下命令即可

# cec-ctl -d0 --monitor-all --ignore=all,poll

通常可以忽略的一个特定消息是轮询消息。逻辑地址分配过程没有定义任何释放所述地址的方法,因此验证地址是否已变为可用的预期方法是定期轮询它,方法是发送一条带有源地址和目标地址但没有有效负载的消息,类似于 ping 的工作方式。如果消息未被确认,则理解为该逻辑地址处的设备“不再需要/使用它”,并将被假定为不再存在。因此,大多数电视会相当频繁地轮询其下游设备以检查其状态,并且使用 --ignore=all,poll(忽略所有轮询消息,无论来源如何)或 --ignore=0,poll(仅忽略来自电视的轮询消息)可以帮助减少日志中的噪音。

参见