☰
RK3588 Android14修改HDMI-CEC设备名:原理与实战
2026/10/10 7:46:08 网站建设 项目流程

最近在调一块 RK3588 的 Android14 方案板,遇到一个说大不大、说小不小的需求:把 HDMI-CEC 上报给电视的设备名字改掉。原本默认名字要么是板卡型号,要么干脆是“Android”,客户想让电视信号源菜单里显示成自己品牌的名字。这个需求表面看就是改个字符串,但真正落地的时候会踩到不少坑:名字是在链路的哪一层生成的?系统属性能不能传下去?为什么电视端半天不刷新?中文名为什么会乱码?这篇文章就把整个排查和改动过程完整记录下来,给正在折腾 RK CEC 的朋友做个参考。

这套改动适合所有基于 Rockchip 平台做 Android 14 定制的工程师,尤其是做电视盒子、投影仪、视频会议一体机这类需要 HDMI-CEC 联动的设备。哪怕你用的是 RK3566、RK3568、RK3588 系列中的任意一款,思路基本一致,只是具体源码路径和编译方式会有差异。下面我按“需求拆解 -> 技术原理 -> 实操落地 -> 问题排查”的顺序来讲,尽量把每一步为什么这么改也讲透。

1. 项目概述与需求拆解

1.1 HDMI-CEC 那台设备显示的“名字”到底从哪来

HDMI-CEC 的全称是 Consumer Electronics Control,它允许通过 HDMI 线串联的设备之间传递控制指令。你按下电视遥控器,机顶盒能跟着开机、切换源,靠的都是 CEC。而在 CEC 协议里,每一台设备都有一个“OSD Name”,也就是电视界面上显示出来的那个名字。CEC 标准里有两个和名字直接相关的操作码:一个是Give OSD Name(0x46),电视主动询问“你叫什么”;另一个是Set OSD Name(0x47),设备主动告诉电视“我叫什么”。

在 Android 系统里,这台设备的“名字”并不是随便取一个本地字符串就行,而是要经过系统服务、CEC 协议栈、底层驱动几个环节,最终封装成 CEC 消息发到 HDMI 总线上。也就是说,你改了产品型号名,不一定等于改了 CEC 名字;改了蓝牙名,也不等于改了 CEC 名字。它们之间既有联系,又是两套完全独立的逻辑。

1.2 需求背后的真实场景

这次需求来自一个电视盒子项目,客户希望投屏的时候,电视信号源列表里直接显示自家品牌名,比如“XX智能盒”的英文标识。这个场景很常见,尤其在酒店、会议室、教育平板等场景里,设备名就是品牌露出的一部分。如果默认名字是“Android”,客户会觉得不够专业,开机一接 HDMI 就露馅了。

除了盒子,游戏机、机顶盒、家庭影院功放也都有类似需求。CEC 名字还影响设备间的自动切换逻辑,比如电视想控制功放音量,电视端列表里显示“Living Room TV”和显示“RX-A”的体验完全不同。所以这个需求虽然只是修改一个字符串,但会直接影响到用户对整机品牌的感知。

2. 技术原理与方案选型

2.1 Android 14 上的 CEC 名称链路

在 Android 14 上,CEC 名称的传递链路大概是这样:系统开机后,CEC 服务初始化时会读取配置,把设备名、厂商 ID、设备类型等参数组装成能力集,然后通过 HAL 或 vendor 协议栈注册到 HDMI 控制器上。当电视发起Give OSD Name请求时,服务直接将配置里的名字填回消息并发送。

这里有一个容易混淆的地方:标准的 Android TV 框架中,CEC 逻辑主要在HdmiControlService,它运行在 system_server 进程里,而名字的默认来源又和系统属性、SettingsProvider 有关。但在 Rockchip 的很多方案里,系统不一定是完整 Android TV,而是带了一套 vendor 自己的 CEC HAL。所以你会发现,改frameworks/base里的默认字符串未必生效,因为到 HAL 层之后可能又被覆盖了。

我在定位这类问题时,通常会先把整条链路画出来,再逐个环节打印日志确认。最怕的是凭经验直接搜“device_name”,搜到一个改一个,结果实际取值的文件根本没被编译进去,白忙一下午。

2.2 Rockchip 平台常见实现差异

Rockchip 各方案的做法并不完全统一,早期 Android 9/10 的 CEC 实现很多直接放在hardware/rockchip/cec目录下,CEC 的 HAL 层有一份CECConfig之类的配置类,专门负责读设备名、厂商、CEC 逻辑地址等参数。到了 Android 14 之后,部分 SDK 已经把服务层迁移到了系统服务里,但 HAL 层依然保留着property_get读取属性的逻辑。

实操中,你只需要记住一点:大部分 RK 平台最终生效的“名字值”,会落在 HAL 层getOsdName()或类似接口里。它内部可能写了死字符串,也可能读某个系统属性。因此,改法取决于你手里的 SDK 到底把默认值写在哪。我建议先全局搜索getOsdName、CEC_DEVICE_NAME、persist.sys.cec这三个关键词,把代码位置拉出来,再决定改动方案,而不是上来就改。

2.3 为什么不要直接硬编码

很多工程师图省事,直接在 HAL 层把return "Android";改成return "MyBox";,编译、烧录、验证,确实一次通过。但等你后面接到下一个产品需求,要改成不同名字时,又得重新改代码、重新编译,非常麻烦。更关键的是,如果产品想在运行时通过设置应用动态修改 CEC 名字,硬编码方案就完全做不到。

我推荐的方案是把名字放到系统属性里,具体是persist.sys.cec.device_name。原因有三点:第一,persist.前缀的属性可以持久化,设备重启后不会丢;第二,它既可以被动写入build.prop,也可以在系统启动后用setprop动态修改,灵活性很高;第三,HAL 层和 Java 层都能通过统一接口访问,能保证名字在服务端、底层的取值一致。

3. 实操过程:从代码到验证

3.1 先把现有默认值查出来

动手改之前,我先在板子上跑了一遍现有环境,确认默认 CEC 名字到底是什么。用adb shell连接设备后,执行下面几条命令:

adb shell getprop | grep -i cec adb shell getprop | grep -i osd adb shell dumpsys hdmi_control | grep -i -E "name|osd" adb shell dumpsys android.hardware.hdmi.cec | grep -i name

在 RK3588 的 Android14 方案里,如果没有额外定制,通常会看到rk3588、Android这类名字。也有可能dumpsys hdmi_control没有输出,这说明当前 CEC 服务还没起来,或者 HPD 没触发,需要先检查 HDMI 线是否连接。

这个步骤看起来简单,但非常关键。很多同事习惯直接改代码,改完烧进去才发现,原来 SDK 里 HAL 读的根本不是这个名字,白白浪费时间。先确认现状,再决定从哪里入手,是排查 CEC 问题的第一步,也是最省时间的一步。

3.2 方案A:通过产品配置写入属性

确认现状之后,我先在产品配置里把默认属性加上。以 RK3588 的 SDK 为例,一般在device/rockchip/rk3588/目录下的.mk文件里,可以追加产品的系统属性和编译配置。

# device/rockchip/rk3588/rk3588.mk PRODUCT_PROPERTY_OVERRIDES += \ persist.sys.cec.device_name=MyLivingRoom

PRODUCT_PROPERTY_OVERRIDES最终会写入到设备上的/system/build.prop或/vendor/build.prop里,系统启动时会自动加载为属性。因为加了persist.前缀,它在设备上会被保存在持久化区域,即使以后运行时被其它设置项覆盖,重启后依然优先取这个默认值。

如果你的 SDK 里已经有用persist.sys.cec.device_name的逻辑,到这一步实际上就已经完成一大半了。但如果 HAL 层没有读这个属性,配置了也不会生效。所以接下来还要确认 HAL 代码,或者直接看后续日志。

3.3 方案B:HAL 层改为读取属性

如果源码里没搜到读取persist.sys.cec.device_name的逻辑,就需要在 HAL 层补一段读取代码。RK 方案里通常能找到一个类似CECConfig或CecHal.cpp的文件,里面提供了获取设备名的方法。

我当时的修改大概长这样:

#include <cutils/properties.h> constexpr char kCecDeviceNameProperty[] = "persist.sys.cec.device_name"; constexpr char kDefaultCecDeviceName[] = "Android"; std::string CECConfig::getOsdName() { char value[PROPERTY_VALUE_MAX] = {0}; property_get(kCecDeviceNameProperty, value, kDefaultCecDeviceName); return std::string(value); }

这段代码的逻辑很简单:优先读取系统属性里的名字,如果属性不存在或为空,就回退到Android。这里的回退值并不是随便写的,它保证了即使产品配置漏了属性,也不会导致 HAL 初始化返回空字符串,从而避免出现“电视上名字空白”这种更难看的情况。

在 Android 14 上还要注意 SELinux 权限。如果 HAL 是一个独立进程,系统属性域是有限制的,可能需要在对应的.te文件里给属性加上允许访问的标签,常见的是allow hal_cec_service cec_prop:file read;之类的声明。没有权限时,property_get不会报错,只会拿到空值,很容易被误判成代码没编进去。

3.4 编译烧录与属性检查

改完之后就要编译。RK Android14 的编译方式不同 SDK 有差异,但大多数都能用原生命令:

source build/envsetup.sh lunch rk3588-userdebug make -j32

如果你的项目用的是公司内部脚本,比如./build.sh -AUCKu之类,那跟着项目文档走就行。编译完后把super.img或对应分区烧录到板子上,重新开机。

开机后第一件事不是接电视,而是先在设备端确认属性是否已经生效:

adb shell getprop persist.sys.cec.device_name

如果输出是你设置的MyLivingRoom,说明 build.prop 这条链路已经通了。接下来再用dumpsys hdmi_control查看当前 CEC 服务里记录的名字,确认是否已经由 HAL 层反馈到上层服务。

3.5 电视端验证

设备端确认没问题之后,才是真正的验收。把 HDMI 线接到电视上,打开电视的输入源列表,等待几秒钟,正常情况下就会看到新名字。如果电视一直显示的旧名字,可以在电视设置里先关闭再开启 CEC,或者把 HDMI 线重新插拔一次。很多电视会把 CEC 设备名单缓存在本地,不会每次都实时刷新。

如果条件允许,我建议用另外一台带 CEC 分析功能的设备,或者直接看 logcat 里HdmiCec相关的消息打印。当电视发出Give OSD Name时,能看到本端回复的 payload,里面应该就是新名字对应的十六进制数据。这样就能确定名字真的发出去了,而不是只在系统本机显示正确。

4. 常见问题与排查技巧

4.1 电视上还是旧名字

这是最容易遇到的问题。代码改了、属性也查到了,电视上还是原来的名字。遇到这种情况,首先要明确一点:电视端有 CEC 设备地址缓存。哪怕你的设备已经发送了新的Set OSD Name,电视端如果没重新扫描,列表里还是会保留旧名字。

解决办法是让设备重新完成一次 HPD 热插拔,或者进电视设置里清空 CEC 设备列表。多数电视在“外部设备”或“HDMI 控制”菜单里都有“重置 CEC 设备”之类的选项。如果还不行,就试试电视全断电重启,让 CEC 协议栈完全重置。千万不要只看设备端,要记住这条命令是双向的,电视不配合,改名就“看不见”。

4.2 重启之后名字又变回去

在设备上临时执行setprop persist.sys.cec.device_name MyBox后,电视上显示了新名字,但重启后恢复默认。这种情况大概率是属性并没有真正持久化,或者属性在持久化前又被某个服务覆盖了。

排查分两步:第一步,重启后再执行adb shell getprop persist.sys.cec.device_name,看属性是否还在。如果已经没了,说明属性写入方式有问题;如果属性还在但电视显示旧名,说明 CEC 服务可能在初始化时读的是另一个配置,比如SettingsProvider里的值或 HAL 内部硬编码值。这时候需要对比日志,定位真正取值的代码路径。

4.3 中文名乱码或显示截断

很多客户希望 CEC 名字直接用中文,比如“智能盒子”,但在实际测试中经常出现乱码或名字变短。原因在于 CEC 标准里 OSD Name 的长度上限是 14 字节,这个字节数不是字符数。一个中文字符在 UTF-8 编码下通常占 3 个字节,几个中文就很容易超出限制。

另外,很多电视的 CEC 解析并不支持 UTF-8,只支持 ASCII 或 Latin-1 字符集,收到非 ASCII 字节后要么丢弃、要么显示成乱码。对于中文客户的这类需求,我一般建议要么用拼音,要么用英文品牌名,比如“ZHINENG BOX”,既符合协议,也避免电视显示异常。如果品牌方坚持要中文,最好做好固件内电视端 CEC 支持程度的专项测试,不能想当然。

4.4 名字被系统设置或其它应用覆盖

还有一种情况:设置里可能有一个 HDMI CEC 名称的编辑入口,用户改名后写入SettingsProvider,这个值优先级高于产品默认属性,于是产品属性改了也被覆盖。Android 的 CEC 服务确实会从Settings.Global或Settings.Secure里读取设备名,不同 SDK 的键名不同,常见的是hdmi_cec_device_name、cec_device_name之类的键。

解决方式是在 HAL 或服务层定义好优先级。我常用的规则是:运行时 Settings 值优先,其次才是persist属性。这样既能保留用户的自定义能力,又保证工厂出厂时默认名统一。如果你希望完全锁死品牌名,不让用户改,那可以直接不读 Settings,只读persist属性。

4.5 常见问题速查表

现象可能原因处理建议
电视显示旧名字电视 CEC 缓存未刷新关闭再开启 CEC,插拔 HDMI,或电视断电重启
重启后恢复默认persist 属性未持久化检查 build.prop 是否生效,确认属性加载顺序
名字空白HAL 返回空字符串保证默认回退值返回“Android”
中文显示乱码电视不支持 UTF-8 / CEC 字节超限改用拼音或英文名,控制 14 字节以内
覆盖了还是旧值Settings 优先级高于属性调整取值优先级,或排查系统设置写入逻辑
SELinux 拒读属性属性文件/域权限未配置添加对应 .te 规则,查看 avc denied 日志

5. 实操总结与个人经验

这次改动让我印象最深的一点是:看似只要换一行字符串的需求,背后其实牵扯到 CEC 协议、系统属性、SELinux、电视端缓存多个环节。如果只改 HAL 里一个return,确实能应付当前项目,但没有真正理解链路,下一次需求变一下名字,又会回到起点重新排查。

我在实际项目中建议先花半小时把读属性的代码和生效流程走一遍,搜出来是哪一层取值,再决定是改PRODUCT_PROPERTY_OVERRIDES、init 脚本,还是 HAL 代码。这个时间投入非常值得。另外,验证时一定要准备一台支持 CEC 的电视,最好再有一台不被缓存干扰的高端设备,否则旧名字缓存很容易让你误判改动无效。

最后再分享一个小技巧:如果你的产品线里有不同型号,建议直接在 HAL 的getOsdName()里统一读属性,出厂配置文件里预留好属性位。这样以后换名字,只需要改配置文件,甚至做成工厂产线校准项,不用每款型号都重新编译一遍代码。这个习惯不仅适用于 CEC 名字,也适用于厂商 ID、逻辑地址等其它 CEC 参数。遇到类似需求时,可以把这个思路复用过去。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询