简介:面向从事高清音视频接口开发、电视与音响系统集成及HDMI合规测试的工程师,这份HDMI 2.1说明文档围绕新版本规范与eARC增强音频回传通道展开,可用于理解48Gbps带宽、VRR、ALLM等特性及音频回传相关要求。压缩包仅含1个PDF文件,大小约1.57MB,正文可见HDMI 2.1 CEC Generic Compliance Test Specification(GCTS)的版本信息、法律声明与测试规范章节,便于按条目查阅合规测试要点。已有2139人学习,适合作为规范速查与测试准备参考资料;读者可从中梳理HDMI 2.1关键特性、eARC功能定位及CEC测试关注项,快速建立从接口标准到合规验证的知识框架。
1. 48Gbps 之外的那条慢线:CEC 为什么决定整套设备的联动
48Gbps、8K、VRR 这些词都挂在 hdmi2.1 的标志上,但真正决定你按下电视遥控器之后功放会不会自己亮、按音量键时调的是谁、待机功耗该降到多少的,是 19 根针里第 13 针那条单线总线。它不能传视频,也不能传音频,一个位的周期长达 2.4 毫秒,比主链路慢了好几个数量级。可就是这条慢线,把一个柜子里各自为政的盒子整合成了一套系统。很多人第一次翻开这份覆盖 CEC 与 eARC 的合规测试规范,会以为它不过是实验室用的打勾清单;读进去才发现,它更像一份记录底层行为的文档:仲裁、地址抢占、电源状态迁移、eARC 握手这些平时看不见的机制,被一条条翻译成能明确判定通过或失败的条款。设备联动失灵时该怀疑哪一层,答案基本都落在这些条款描述的那几百毫秒里。
2. 一条线怎么不冲突:CEC 的电气、时序与仲裁
2.1 开漏输出与线与逻辑:冲突为什么不烧芯片
CEC 总线上没有主从时钟线,也没有片选,所有设备对应的引脚直接并联在同一根铜线上。能做到这一点,靠的是电气层的设计选择:每个设备的输出级都是开漏(或漏极开路)结构,只能主动把线拉低,释放后由上拉电阻把线拉回高电平。常见做法是 3.3V 经一个几十 kΩ 量级的上拉电阻(典型 27kΩ)接到总线上,逻辑低要求拉到 0.6V 以下,逻辑高要在 2.5V 以上。这个阻值不是随便选的,它要和整条线缆的分布电容一起决定上升沿的时间常数;阻值太小功耗大,阻值太大边沿变缓,在 1.05ms 的采样点上就会读出亚稳态。
这种“任何人都能拉低、只有所有人都释放才为高”的结构就是线与。它的直接好处是:两个设备同时发送、一个写 1 一个写 0 时,总线呈现为低电平,不会出现推挽输出那种一端拉高、一端拉低的短路电流。冲突在场效应层面被化解成“低电平获胜”,芯片毫无损伤,协议层只需要判断“我输了”。所以 CEC 从设计之初就是一个多主、无中心、靠电气特性实现隐式仲裁的总线,而不是先定一个主机再分配时隙。这份规范里后面所有关于 polling、地址分配、重传的条款,都建立在“总线是线与的”这个前提上。
2.2 2.4ms 一位:采样点为什么固定在下降沿后 1.05ms
CEC 的一帧由起始位、8 个数据位、EOM 位、ACK 位组成,共 10 位,每位固定 2.4ms。它不靠波特率同步,而是把信息编码在低电平的宽度上:逻辑 1 是低 0.6ms 加高 1.8ms,逻辑 0 是低 1.5ms 加高 0.9ms。两种码型总长相同,区别只在低电平持续多久。接收端不去测量低电平宽度,而是在下降沿之后一个固定时刻采样——典型值是 1.05ms。这个时刻是两种码型唯一能分开的窗口:逻辑 0 在 1.05ms 时还在低电平里(要低到 1.5ms),逻辑 1 早就释放回高了(0.6ms 就结束)。
下面这段固件片段把发送和采样两侧的逻辑写在一起,能看清为什么采样点必须固定:
/* CEC 位时序,单位 us。一位固定 2400us,采样点固定在下降沿后 1050us */ #define CEC_BIT_PERIOD_US 2400 #define CEC_SAMPLE_DELAY_US 1050 /* 下降沿后采样,此时 0/1 电平已分离 */ #define CEC_LOW_ZERO_US 1500 /* 逻辑 0:低 1500us + 高 900us */ #define CEC_LOW_ONE_US 600 /* 逻辑 1:低 600us + 高 1800us */ static void cec_send_bit(int bit) { cec_drive_low(); /* 开漏拉低 */ delay_us(bit ? CEC_LOW_ONE_US : CEC_LOW_ZERO_US); cec_release(); /* 释放,上拉拉回高 */ delay_us(CEC_BIT_PERIOD_US - (bit ? CEC_LOW_ONE_US : CEC_LOW_ZERO_US)); } static int cec_sample_bit(void) { while (cec_read_pin() != 0) { } /* 等下降沿 */ delay_us(CEC_SAMPLE_DELAY_US); /* 对齐到统一采样点 */ return cec_read_pin(); /* 仍为低 -> 逻辑 0 */ }采样点选 1.05ms 而不是中点,是为了给两端留出不对称但更实用的容差:逻辑 1 的释放沿可能因为上拉太弱而变得迟缓,所以采样点靠近 0.6ms 那一侧;逻辑 0 的低电平一直持续到 1.5ms,离 1.05ms 还有 450ms 的余量,足够吸收线缆延迟和收发器的抖动。参数改动的边界也很清楚:CEC_LOW_ONE_US只要没短到采样点之前就释放,CEC_LOW_ZERO_US只要没长到吃掉整位周期,链路就能维持。真正容易出问题的是上升沿时间,也就是上拉阻值和线缆电容的乘积,它一旦超过采样窗口的余量,读出来的位就会随机翻转。
2.3 仲裁与重传:发送失败的一方如何自行发现
发送方每写一位都要同时读回总线。写 1 时它释放总线,如果读回是低,说明另一个设备正在写 0,自己输了仲裁,必须立刻停止发送、转为接收,把这一帧剩下的内容听完。因为低电平获胜,输的一方只可能是“写 1 被别人的 0 压住”的那一个,所以仲裁结果天然与地址、优先级无关,只与“谁先释放、谁释放后被压住”有关。这也是为什么 CEC 不允许在帧中间插入等待,一旦开头几位输了,整帧就让出去。
帧与帧之间还有一道限制:信号自由时间。设备在总线空闲之后必须等待一段静默期才能发起新帧,这一段时长按发起者的身份分档,从 2.4ms 量级到接近 100ms 量级不等,新发起者、重传者、同一帧内的块间隔各用各的档位。它解决的是“刚结束一帧,所有设备同时抢着说话”的问题,用不同的等待时长把碰撞概率压下去。ACK 缺失时会触发重传,规范给出的重传上限是 5 次;超过就上报失败,由上层决定是重试整个流程还是降级。这套机制没有什么巧妙的地方,全是血泪经验堆出来的时序条款,但少了任何一条,多设备同时上电时总线就会陷入无休止的碰撞。
2.4 15 个逻辑地址:polling 消息到底在问什么
CEC 用 4 个位表示逻辑地址,一共 15 个可用值,每个值对应一种设备角色,而不是物理位置:
| 逻辑地址 | 角色 | 典型设备 |
|---|---|---|
| 0 | TV | 电视 |
| 1 / 2 / 9 | Recording Device | 录像、录制类设备 |
| 3 / 6 / 7 / 10 | Tuner | 机顶盒、调谐器 |
| 4 / 8 / 11 | Playback Device | 播放器、游戏机 |
| 5 | Audio System | 功放、回音壁 |
| 14 | Specific Use | 特殊用途 |
| 15 | Unregistered / 广播目标 | 未注册设备或广播 |
设备上电后做的第一件事是 polling:向自己想要的地址发一条只有头块、没有数据块的消息,目标地址填那个候选值。这条消息的特殊之处在于,如果该地址已被占用,占用者必须回 ACK;没人回 ACK,说明地址空闲,发送方随即广播一条“我占了这个地址”的声明。若候选地址被占,就退到下一个候选,全部候选都试完仍失败,只能落到 15(Unregistered),此时它不能作为被寻址方,只能以广播方式发言。整个过程没有任何中心协调者,完全靠 ACK 的有无做分布式选举。
/* 简化版地址申请状态机:依次探测候选地址,失败则降级到 Unregistered */ static uint8_t cec_claim_address(const uint8_t *cands, int n) { for (int i = 0; i < n; i++) { if (cec_poll(cands[i]) == CEC_NO_ACK) { /* 该地址没人应答 */ cec_broadcast_claim(cands[i]); /* 广播:我占了 */ return cands[i]; } /* 收到 ACK 说明被占,但对方可能刚掉电,需重试若干次再放弃 */ } return 0x0F; /* 全被占,降级为未注册 */ }这段状态机看起来简单,实际的问题都藏在cec_poll里面:poll 超时阈值、ACK 窗口的采样位置、对方“已经掉电但地址还没释放”的残留状态,每一项都会让选举结果偏离预期。所以规范把 polling 和地址分配单独列成两组测试(7.4 与 7.5),不是为了凑章节,而是这两组恰好覆盖了分布式选举里最容易失败的地方。
3. 103 页装了什么:GCTS 的拆分逻辑与 CDF 裁剪
3.1 一本变六本:Version 2.1 为什么要拆文档
看修订历史会发现一个转折点:Version 2.1 之前,所有测试项都塞在一本文档里,靠章节号区分 Source、Sink、Repeater、CEC。到 2.1 版,整套 GCTS 被按目标产品类型或特性拆成了顶层文档加若干子文档:Source、Sink、Repeater、CEC、eARC、Cat3 连接器各一本,顶层只保留法律声明、术语定义、通用能力声明表单和跨文档引用。这个拆法的动机不难理解:一份文档同时承载六类设备的测试项,任何一类改一条,整本文档的版本号都要动,引用它的实验室和厂商全得重新走一遍确认流程。拆开之后,eARC 的测试项演进不会污染 Source 的条款,责任边界也清楚了。
代价是引用关系变复杂。CEC 这本里大量条款要靠顶层文档的定义和附录表单才能执行,规范里也因此反复声明“顶层文档的法律声明与附加声明通过引用并入本文件”。换句话说,拆分不是把内容切碎,而是把“稳定的公共部分”和“易变的测试部分”分离,前者只维护一份,后者按需迭代。这种文档工程上的取舍,和代码里把公共依赖抽成基础库是同一个思路,只不过规范没有重构工具,只能靠引用条款硬绑。
3.2 Test ID 与修订历史:编号不变、内容可换的可追溯性
测试项的编号体系是另一处值得琢磨的设计。Source 侧用 HF1-xx,Sink 侧用 HF2-xx,Repeater 侧用 HF3-xx,CEC 侧用 HF4-x-x。编号一旦分配就不再复用,即使测试内容被大改也保留原号。翻修订历史能看到这种做法的痕迹:2.0b 版新增了几条 HDR 相关测试并补了对应的能力声明字段,2.0d 版加了 E-DDC 的电气与时序测试项,2.0c 版则大量是“修正某测试的第几步”“删除某测试的第几步”。这些修订如果换了编号,已经通过这些测试的产品就得重新对照,认证记录会断链;保留编号、只改内容,历史结果就仍然可追溯,只是需要额外记录“通过的是哪个版本”。
这种“编号稳定、内容可变”的约定,本质上是把测试项当成一种带版本语义的接口。它带来的一个隐性成本是:光看编号无法判断测试严格程度,必须连同文档版本一起引用。所以在跨团队协作时,只写“已通过 HF4-1-6”是不够的,还得说明依据的是哪一版规范,否则两边对同一条测试的理解可能差了整整一轮修订。
3.3 CDF:自我申报如何决定你被考哪几张卷子
整套测试的入口是一张表:DUT 能力声明表。文档给了两份,一份是通用的设备能力声明,一份是 CEC 专用的能力声明表单,另外还提供了面向旧版设备的最小声明集合。设备厂商先在这张表上勾选自己支持哪些功能、支持到什么程度,测试机构再据此从完整的测试集里挑出适用项。没申报的功能不测,但一旦申报就必须通过;申报了却不实现,等于自己给自己挖坑。
这是整套合规体系里最关键的一次取舍:它把“功能是否存在”的判定权交给厂商,把“功能是否正确”的判定权留给测试。好处是测试矩阵可以随产品形态裁剪,一台只做 sink 的电视不必去跑 Source 的几十条用例;坏处是申报本身成了合同,任何夸大都会在测试台上暴露。我一般会把这理解为一种责任前置:规范不假设知道你是什么设备,只要求你先把身份说清楚,然后按你说的身份来验。附录里那份“最小声明集合”尤其能说明问题——它定义的是“想接入这套生态,至少得声明什么”,相当于给互操作划了一条底线。
3.4 一个用例的解剖:Setup、Stimulus、Observation
把任意一条 CEC 测试项拆开,结构都差不多:先摆好设备与拓扑(Setup),再注入一条消息或一次状态变化(Stimulus),然后观察总线上的响应(Observation),最后对照通过判据。以地址分配类测试为例,Setup 会规定总线上有哪些角色、哪些地址已被占用;Stimulus 是让被测设备上电;Observation 是抓它发出的 polling 消息和随后的声明广播,看它是否落在预期地址上、是否在规定的静默期之后才发声。
CEC 测试组的分工也印证了这种结构:
| 测试组 | 覆盖内容 |
|---|---|
| 7.1 低层 | 位时序、电气层行为 |
| 7.2 通用协议 | 帧结构、操作码、广播与定向 |
| 7.3 时序 | 信号自由时间、重传间隔 |
| 7.4 / 7.5 | polling 与逻辑地址分配 |
| 7.6 | 电源状态迁移 |
| 7.7 / 7.8 / 7.9 | 路由切换、遥控透传、OSD 名称传输 |
| 7.10 / 7.11 | 系统音频控制、音频回传控制 |
| 7.12 / 7.13 | 厂商自定义消息、转换线缆 |
值得注意的是 7.10 和 7.11 这两组。它们名义上属于 CEC 测试,实际服务的却是音频回传——音量键能不能调到功放、音频通道由谁发起,这些行为全部走 CEC 消息。这也是理解 eARC 的入口:音频通路换了,控制面并没有换。
4. eARC:音频回传为什么要另起一条通道
4.1 ARC 的三个硬伤:带宽、格式、依赖
早期的音频回传走的是同一对差分线,能力被压到与消费级光纤协议相当的水平:带宽在 1Mbps 量级,只能承载双声道未压缩音频或压缩的 5.1 码流。想传无损多声道、想传基于对象的音频格式,带宽根本不够。第二个硬伤是格式表达能力弱,回传通道没有一套足够细的能力协商机制,设备之间只能按最小公约数走。第三个硬伤最容易被忽略:这条通道的建立和断开本身就是 CEC 消息驱动的,发起、确认、终止各有一条消息,谁先发、什么时候发都有规定。所以音频回传从来不是一个独立功能,它是挂在 CEC 上的一个附属流程。
这三条加起来,导致一个很别扭的局面:主链路已经能跑 8K 了,回传链路还在传双声道。要解决它,只有两条路——要么在原有通道上提高调制效率,要么彻底换一条物理路径。eARC 选了前者,但把用法改得很彻底。
4.2 差分对上的共模通道:eARC 的物理路径
eARC 仍然用原来那对差分线,但不再把它当普通差分信号用,而是利用差分对上的共模分量做单向传输,方向固定为从显示端到音频端。这样做的直接好处是不需要新增针脚和线缆,现有的高速线缆在物理层就能承载;代价是对共模抑制、终端匹配和线缆一致性的要求提高,接收端要做共模提取,发送端要保证两路输出的共模分量足够干净。带宽因此提升到几十 Mbps 量级,足以承载 192kHz 的多声道未压缩音频,以及无损压缩的多声道码流和建立在其上的对象音频格式。
这条通道是单向的,这一点常被误解。eARC 只负责把音频从显示端送回音频端,反方向的音频仍然走主链路的 TMDS/FRL 通道。所以一套完整的 eARC 系统里,音频实际上有两条路径,具体走哪条取决于播放内容由谁解码:显示端自己解码就用 eARC 回传,外部播放器解码就走主链路直通。理解了这个方向性,才能理解为什么 eARC 的建链流程里,发起方永远是显示端。
4.3 Discovery 与 CDS:能力协商发生在建链之前
eARC 的协商分两条路走。一条是 CEC 消息,负责“建不建、断不断”:显示端发出初始化请求,音频端确认,通道建立后双方各自维护状态,任何一端要断开都要发终止消息。另一条是 EDID 里的能力数据结构,负责“能传什么格式”:里面声明支持的音频格式、最大声道数、采样率上限、是否具备 eARC 能力。两条路的分工很清晰——CEC 管控制面,EDID 管能力面。
顺序上,能力面必须先于控制面。显示端上电后先读音频端的 EDID,拿到能力数据结构,确认对方支持 eARC 以及支持到哪一档格式,再决定是否发起 CEC 的建链请求。这个顺序不是随意定的:如果先建链再发现格式不支持,就得走一遍“建立—发现不匹配—终止—降级回旧通道”的流程,中间会有明显的静音窗口。反过来,先读能力再建链,最坏情况也只是根本不建链,直接走旧通道,用户感知到的只是“没有环绕声”,而不是“声音断了几秒”。这类顺序上的讲究,在规范里往往只用一两句话带过,但实现时踩不踩坑,差别就在这一两句话。
4.4 与 CEC 7.10 / 7.11 的耦合:音频链路换了,控制面没有换
把 eARC 和 CEC 放在一起看,会发现一个有意思的分层:音频数据走了新通道,但音量、静音、系统音频模式切换这些控制行为,仍然由 CEC 消息承载。7.10 那组测试覆盖的是系统音频控制——显示端的遥控器按下音量键,消息发给音频端,音频端调整后回报当前状态;7.11 那组覆盖的是音频回传通道的控制流程——谁发起、谁确认、异常时怎么终止。这两组测试在 eARC 时代一条都没删,原因就在于控制面没有变。
这带来一个实际后果:eARC 的问题经常表现为 CEC 的问题。音频通道建不起来,可能不是共模通道的电气问题,而是初始化消息没发出去或者没收到确认;音量调不动,可能不是音频端的问题,而是系统音频模式没切过去。排查时必须把两层分开看:先确认 CEC 层的消息是否按预期收发,再确认 eARC 层的通道是否建立,最后才去怀疑音频格式和时钟。反过来,只盯着音频链路查,很容易在错误的方向上耗掉一整天。
5. 联调避坑:五个只在实验室里露头的现象
现象一:冷启动时地址申请偶发失败,重启一次就好。原因通常不在协议栈,而在上电时序。主控还没初始化完,CEC 控制器已经开始收发了,此时读回的电平和软件状态对不上,poll 的 ACK 判定被误判成“地址被占”,设备一路降级到未注册。 解决是把 CEC 控制器的初始化放在主控供电稳定之后,并在 poll 前加一段固定延时;同时给 poll 加重试,单次 ACK 缺失不直接放弃候选地址。
现象二:消息间歇性收不到 ACK,示波器上看波形“很正常”。多数是上升沿时间超标。上拉阻值偏大、线缆偏长、分支电容偏大,三者叠加后边沿变缓,在 1.05ms 采样点上刚好落在阈值附近,读出来的位随机翻转。波形整体看没问题,只有把时间轴放大到采样点附近才能看出来。 解决是量采样点处的实际电平,而不是量高低电平的稳态值;必要时减小上拉阻值、缩短线缆、去掉多余分支。
现象三:待机状态下 CEC 完全不响应,唤醒后第一条消息总是丢。原因是低功耗设计把 CEC 控制器一起关了,或者只用了普通 GPIO 中断。普通中断会被总线上的噪声触发,主控醒来后发现没有有效消息又睡回去,真正的那一帧正好在这个窗口里被错过。 解决是用带硬件滤波的唤醒路径,只对合法的起始位序列产生唤醒;唤醒后不要立刻收发,先重新同步到空闲状态再参与总线。
现象四:eARC 能出声,但切换内容时偶发爆音或短暂断音。常见原因是能力声明和实际链路能力不一致:EDID 里声明了高采样率多声道,实际线缆或接收端只能勉强承载,链路裕量不足,格式一切换就崩。另一种情况是通道刚建立就立刻开流,没等确认消息走完。 解决是让声明保守一档,把裕量留给最坏线缆;开流前等到确认消息确实收到,再启动音频通路。
现象五:多设备同时上电,广播消息被两个设备同时 ACK。这是地址冲突的典型表现:两个设备都认为自己拥有同一个逻辑地址,广播帧到来时一起拉低 ACK 位,发送方只看到一次 ACK,冲突被掩盖。等到定向消息发过去,才发现有两个响应者。 解决是在地址申请成功后广播声明,并在运行期定期重发 polling 自检;发现自己的地址被别人 ACK,主动降级重新申请,而不是继续争抢。
6. 把兼容性写成条款,代价是什么
这套规范最核心的设计取舍,是把“能不能用”翻译成“能不能判定”。翻译的过程必然有损:连续的行为被切成离散的条款,模糊的体验被压成二值的通过或失败。比如音频回传的建立过程本来是毫秒级的连续状态迁移,落到测试里就变成“在规定窗口内是否收到某条确认消息”;再比如地址分配本来是分布式选举,落到测试里就变成“上电后是否在预期地址发出 polling”。这种有损翻译换来了可复现、可对比、可追责,代价是实现方必须按条款的字面意思办事,而不能按自己的直觉优化。
第二个取舍藏在文档结构里。按设备类型拆分成多本,让每类产品的测试项可以独立演进,但把“跨文档引用”变成了常态。CEC 这本里大量条款要靠顶层文档的定义和附录表单才能执行,读的时候必须两份对照。这种拆分对维护者友好,对读者不友好,属于典型的以长期成本换短期清晰。
第三个取舍在兼容性上。新通道明明可以做得更彻底,却必须保留旧通道的完整路径,还要让新旧两条路径在同一套控制消息下共存。这意味着实现方要同时维护两套状态机,并且在任何一个时刻都能正确回答“现在走的是哪条路”。看起来是包袱,实际上是这套生态能持续运转的前提:任何一次升级都不允许把已有设备踢出局。
验证这些取舍是否被正确实现,最笨也最有效的办法是分层抓包:先抓 CEC 层的消息序列,确认发起、确认、终止的时序;再抓 EDID 读出的能力数据结构,确认声明和实际一致;最后才去看音频通道的误码和时钟。三层都对齐了,问题基本就定位在具体某一条条款上,而不是在“玄学”里打转。
从那以后我每次接手涉及多设备联动的项目,都会强制走一遍这个三层顺序,先看消息、再看声明、最后看数据,绝不从数据层往上猜。希望帮到你。
本文还有配套的精品资源,点击获取