简介RC6遥控解码程序是一份面向嵌入式开发者和家电自动化爱好者的C语言源码资源用于解析飞利浦RC6红外遥控协议信号并生成对应控制指令适用于智能家居控制、遥控器替代或逆向工程项目。压缩包内仅1个C文件体积约1KB便于直接阅读与移植到各类MCU工程中文件虽小但涵盖初始化、中断捕获、脉冲分析、数据校验和指令解析等关键环节结构清晰适合中初级开发者研读。目前已有359人学习下载说明该资源在简易红外解码场景中具备一定参考价值。通过研读此代码可以掌握RC6协议起始码、地址码、命令码与校验码的解析思路理解ASK调制下红外脉冲宽度测量的实现方法同时为自行编写其他IR协议解码器提供可复用的框架。1. RC6遥控解码程序到底是什么一个红外遥控方案的价值与适用场景“RC6遥控解码程序”这个标题看起来只是一个孤立的模块但它背后是红外遥控协议里最容易被低估的一类曼彻斯特编码。做智能家居控制面板时我接过一台老电视的遥控器按键值死活读不对后来才发现那台遥控器用的不是常见的脉宽式遥控协议而是 RC6。RC6 遥控解码程序就是专门把红外接收头输出的脉冲序列还原成地址和命令码的一段代码。它适合所有需要做红外学习、万能遥控、红外转发场景的嵌入式开发者。如果你只写过脉宽式解码这篇正好帮你把协议解码的另一条分支补上。注意RC6 的解码思路和 NEC 这类协议完全不同照搬老代码一定会翻车。2. RC6协议帧结构与曼彻斯特编码解码前必须吃透的时序参数很多开发者拿到红外信号第一反应是量高低电平宽度然后按脉宽协议去解析。RC6 用这套思路会绕很大的弯路因为它的信息不在单段电平宽度里而在电平的翻转方向和翻转节奏里。所以解码前必须先把帧结构和曼彻斯特编码的时序参数吃透否则后面状态机写得再漂亮也只是一堆乱码的搬运工。2.1 RC6帧的组成引导脉冲、起始位、模式段与地址命令段RC6 协议是消费电子领域一种常见的红外遥控协议采用 38kHz 载波调制逻辑位时间 T 的典型值是 444us。一次完整的按键帧不是从数据位开始的而是由一个很醒目的引导脉冲开头。以最常见的模式 0 为例一帧 RC6 数据按顺序由这几部分组成段名长度/宽度说明引导脉冲高段6T 高电平红外发射管连续载波用于让接收端确认“帧开始”引导脉冲低段2T 低电平引导结束的标志起始位2T1T高 1T低固定为曼彻斯特编码的 1用于时间同步模式段3 位常见模式 0 是 000翻转位1 位每次按键按状态翻转用于区分重复按键地址段8 位设备地址命令段8 位按键命令码所以一帧完整的 RC6 模式 0 数据从起始位开始算共有 1 3 1 8 8 21 个有效位。加上引导脉冲的 8T 时间总时长约 8T 21 × 2T 50T在 T444us 时约 22.2ms。这个时长对后续的超时判断很有用如果解码器等了一个帧周期还没等到下一个位边沿大概率是遥控器松手了可以复位状态机。注意有些资料会把引导脉冲也算成“起始位”的一部分这容易造成状态机设计混乱。我的习惯是严格把引导脉冲当作前导码处理起始位只指那 1 位曼彻斯特编码的 1。两者的判定阈值不同引导脉冲是 6T 和 2T起始位是 1T 和 1T。混在一起后避坑章节里那条“偶发解出乱码”多半就是这么来的。2.2 曼彻斯特编码与载波调制解码盯边沿不盯电平红外发射端先把数据按曼彻斯特规则变成基带波形再用 38kHz 载波调制发射。接收头内部完成放大、滤波和解调输出的是反相后的基带电平有载波时输出低电平无载波时输出高电平。因此我们拿到的信号已经去掉载波剩下的是脉冲序列但极性是反的。程序里如果直接按接收头输出电平去判断“高电平有信号”很容易把引导脉冲高低段整个弄反。RC6 使用的曼彻斯特编码可以这样理解每个数据位占 2T 时间位周期中点在 1T 处一定会发生一次电平翻转。若前半位为高、后半位为低表示逻辑 1若前半位为低、后半位为高表示逻辑 0。所以位值是由翻转方向决定的。从波形上看逻辑 1 是“先高后低”逻辑 0 是“先低后高”。每一个位周期内必然有一次中间翻转而在位与位的边界处如果相邻位相同就没有翻转如果相邻位不同就多一次翻转。也就是说RC6 的信息不在电平持续多久而在“边沿出现在什么时刻、朝哪个方向翻”。一次有效的曼彻斯特位其前半段电平一定等于该位的值。利用这个特点我们可以用固定周期 T 去采样电平每个位周期采两次取前半次采样的电平就能得到位值再校验两次采样是否相反。这种做法比逐个测量脉冲宽度更抗抖动也是后面代码里采用的核心策略。2.3 用逻辑分析仪抓真实波形动手测量位周期T在写代码之前我强烈建议先用逻辑分析仪抓一次完整按键波形。市面上常见的 8 通道入门逻辑分析仪完全够用不需要示波器。操作步骤把红外接收头输出引脚接到逻辑分析仪的通道 0GND 接好设置采样率不低于 1MHz然后对着接收头按遥控器上一个按键触发条件设为下降沿抓一段约 50ms 的波形。抓到的波形里你会看到典型的引导脉冲和后续密集的翻转。用游标测量引导脉冲高段的时间再除以 6得到的值就是 T。例如高段实测 2.664ms则 T ≈ 444us。不同厂商的 RC6 遥控器可能在 T 上有偏差常见值有 444us、333us 甚至 300us 左右。绝不能只按手册写的 444us 去写死程序至少要留 ±25% 的容差或者做成自动测量。参数表里最关键的三个值参数典型值作用T444us半位周期决定采样定时器周期引导高段6T帧同步阈值引导低段2T确认引导结束用逻辑分析仪确认这组参数后再进入硬件和代码设计基本可以一次通过。跳过这一步直接写程序后续所有“玄学问题”都会归结到 T 不匹配上。3. 从硬件到中断搭建RC6解码的最小系统解码程序要稳定运行硬件和中断配置比解码逻辑本身更早决定成败。RC6 对时序要求高但也没高到必须用专用 IR 解码芯片。普通单片机的一体化红外接收头加一个输入捕获定时器就够了。这章讲清楚接收头怎么接、捕获怎么配、边沿数据怎么缓冲。3.1 红外接收头选型与接法38kHz载波与反向输出选一体化红外接收头不要选光敏二极管加放大电路的分离方案。一体化接收头内部已经包含红外 PIN 二极管、自动增益控制、带通滤波和解调器输出可以直接进单片机 GPIO。选择时注意载波频率要和遥控器匹配RC6 普遍使用 38kHz 载波所以选中心频率 38kHz 的接收头即可。引脚通常有三个电源、GND、信号输出。供电电压常见 5V 或 3.3V购买时确认接收头是否支持你的系统电压。输出引脚是开集电极或类似结构需要外接上拉电阻典型值 4.7kΩ 到 10kΩ。如果不接上拉输出低电位恢复高电位会变慢直接导致后续 1T 间隔测量不准。电源引脚处建议并联一个 10uF 电解电容和一个 0.1uF 瓷片电容靠近接收头放置。RC6 连续按帧时接收头功耗变化明显电源纹波会让输出波形抖动这个细节很多人忽略。接收头输出电平是反相的收到 38kHz 载波时输出低电平空闲时输出高电平。所以解码程序里把“载波存在”记为低、“无载波”记为高不要在这个地方做习惯性反补。引导脉冲高段在发射端是连续载波对应到接收头引脚其实是持续约 6T 的低电平。后面所有逻辑都以“接收头引脚电平”为准不做感性的正反转换。3.2 定时器捕获模式用输入捕获记录脉冲边沿时间戳输入捕获非常适合解析像 RC6 这种“边沿驱动”的协议。把接收头输出接在定时器的捕获通道引脚上配置定时器同时捕获上升沿和下降沿。每次捕获事件触发中断中断里读取捕获寄存器得到当前边沿的时间戳。之所以用捕获而不是 GPIO 轮询是因为 RC6 最短边沿间隔只有 1T约 444us主循环里轮询很容易丢失边缘抖动而捕获由硬件完成时间精度有保障。我一般会用一个 16 位或 32 位定时器工作在某一个时基频率下。假如系统时钟 72MHz定时器预分频到 1MHz则每计数 1 代表 1us。这时 T444 对应计数 444。这个数值要放进宏定义后续所有阈值都以它为基准。需要特别处理定时器溢出如果两个边沿间隔超过定时器最大计数值时间戳相减会出错。常见做法是在溢出中断里维护一个溢出次数变量计算时间差时把溢出部分补回去或者选择具备更大位宽的定时器并在配置时保证一个 T 的时间差远远小于溢出周期。捕获极性要配置为双边沿触发这样才能记录每个高低跳变。有些单片机输入捕获只支持单边沿就需要用两个通道配合或使用外部中断加定时器读值。无论哪种方式最终目标都是获得“边沿时刻”和“边沿方向上升或下降”两个信息。时间戳单位统一用定时器计数不要中途换成微秒避免除法误差。3.3 边沿时间戳环形队列给解码器提供原始素材中断里做完整解码不是不行但 RC6 的状态机包含多位解析且可能涉及回调放在中断里会让系统响应变差。更稳妥的做法是中断只把边沿时间戳和边沿方向推进一个环形队列解码状态机在主循环或低优先级任务里消费这个队列。这样中断服务程序保持极短也方便后续扩展自定义学习协议。一个简单实现如下#define EDGE_Q_SIZE 64 typedef struct { uint32_t time; // 定时器捕获值 uint8_t level; // 该边沿后的电平0低1高 } edge_t; static edge_t edge_q[EDGE_Q_SIZE]; static volatile uint8_t q_head; static volatile uint8_t q_tail; void edge_q_push(uint32_t time, uint8_t level) { uint8_t next (q_head 1) % EDGE_Q_SIZE; if (next q_tail) { // 队列满丢掉最旧的一条 q_tail (q_tail 1) % EDGE_Q_SIZE; } edge_q[q_head].time time; edge_q[q_head].level level; q_head next; } uint8_t edge_q_pop(uint32_t *time, uint8_t *level) { if (q_head q_tail) return 0; *time edge_q[q_tail].time; *level edge_q[q_tail].level; q_tail (q_tail 1) % EDGE_Q_SIZE; return 1; }这段代码里q_head是写指针q_tail是读指针。队列满时直接丢弃最旧边沿保证新边沿能写入。RC6 一帧约 50T即使 T444us一帧也就几十个边沿64 深度足够。注意q_head、q_tail在中断和主循环之间共享需要声明为volatile如果 MCU 平台对原子性有要求还需要加临界区保护一般单字节读写已经够用。调用时机上输入捕获中断里只做一次edge_q_push解码器循环里不断edge_q_pop并喂给状态机。队列为空时主循环可以休眠或做其他任务。这种生产者/消费者模型让解码逻辑独立于硬件捕获后面调试时可以直接往队列里灌模拟边沿非常方便。4. 状态机解码RC6完整代码与逐段说明有了边沿队列接下来就是核心的解码程序。我采用“边沿状态机 半位定时采样”的混合方案先用捕获到的边沿确认引导脉冲和起始位然后在起始位结束时启动一个半位周期定时器以 T 为周期连续采样电平每两次采样合成一个数据位。这样的优势是数据阶段不再依赖边沿中断而是靠稳定时钟采样对遥控器发出的轻微抖动有更高的容忍度。4.1 主状态机从搜台到锁定的五个状态状态机负责识别帧头和起始位它的输入是边沿事件。只有状态成功走到起始位结束定时采样才会启动。下面是核心的状态定义与转移逻辑typedef enum { RC6_IDLE, RC6_LEADER_HIGH, RC6_LEADER_LOW, RC6_START_HIGH, RC6_START_LOW } rc6_sync_state_t; static rc6_sync_state_t sync_state; static uint32_t last_edge_time; static uint32_t edge_delta; // 判断 delta 是否在 target_ticks 的容忍范围内 static int in_tolerance(uint32_t delta, uint32_t target_ticks, uint32_t tolerance_percent) { uint32_t low target_ticks * (100 - tolerance_percent) / 100; uint32_t high target_ticks * (100 tolerance_percent) / 100; return delta low delta high; } void rc6_on_edge(uint32_t edge_time, uint8_t level_after_edge) { // level_after_edge: 0低, 1高 edge_delta edge_time - last_edge_time; last_edge_time edge_time; switch (sync_state) { case RC6_IDLE: if (level_after_edge 1) { sync_state RC6_LEADER_HIGH; } break; case RC6_LEADER_HIGH: // 期望引导脉冲高段结束低电平下降沿时长约 6T if (level_after_edge 0 in_tolerance(edge_delta, 6 * T_TICKS, 25)) { sync_state RC6_LEADER_LOW; } else { sync_state RC6_IDLE; } break; case RC6_LEADER_LOW: // 期望引导脉冲低段结束高电平上升沿时长约 2T if (level_after_edge 1 in_tolerance(edge_delta, 2 * T_TICKS, 25)) { sync_state RC6_START_HIGH; } else { sync_state RC6_IDLE; } break; case RC6_START_HIGH: // 起始位前半段高电平结束后下降沿时长约 T if (level_after_edge 0 in_tolerance(edge_delta, T_TICKS, 25)) { sync_state RC6_START_LOW; } else { sync_state RC6_IDLE; } break; case RC6_START_LOW: // 起始位后半段低电平结束后上升沿时长约 T if (level_after_edge 1 in_tolerance(edge_delta, T_TICKS, 25)) { // 起始位已锁定启动半位采样定时器 rc6_start_half_timer(); sync_state RC6_IDLE; // 采样阶段由定时器接管 } else { sync_state RC6_IDLE; } break; } }这段代码里T_TICKS是前面测得的 T 对应的定时器计数。in_tolerance把容差写成函数默认给 25%实际项目中我用 20% 到 30% 都能工作太严容易漏帧太松容易被干扰误触发。注意RC6_IDLE状态下输入捕获通道只对上升沿敏感因为引导脉冲高段在接收头输出端是低电平但我们要在边沿事件里找的是“电平变成高”的上升沿。实际接入时通过逻辑分析仪确认极性后保持代码里level_after_edge的判断一致即可。状态机走到RC6_START_LOW成功意味着起始位1T 高 1T 低已经收到且当前时刻正好是数据位周期的起点。此时启动半位采样定时器是完美的同步点。如果中间任何一步不满足状态机立即回到RC6_IDLE不浪费 CPU 继续期待一个不完整的帧。4.2 位周期判定与半位采样曼彻斯特解码的落地实现半位采样定时器要求第一次中断在启动后的 0.5T 时刻之后每隔 T 中断一次。这个 0.5T 是故意错开的使得每次中断正好落在曼彻斯特位窗口的前半段中间或后半段中间。中断服务程序里读取接收头输出电平并和上次采样电平配对。static uint8_t half_index; // 当前采的是半位组里的第几个0前半1后半 static uint8_t half_buffer[2]; static uint8_t bit_buffer[21]; static uint8_t bit_count; static volatile uint8_t sampling_active; void rc6_on_half_tick(void) { if (!sampling_active) return; uint8_t level rc6_read_pin(); // 读取接收头输出引脚电平 half_buffer[half_index] level; half_index ^ 1; if (half_index 0) { // 说明刚填完一个位周期的两次采样开始组合 if (half_buffer[0] half_buffer[1]) { // 同一前半后半电平相同不是合法曼彻斯特位立即终止 rc6_stop_sampling(); return; } if (bit_count 21) { bit_buffer[bit_count] half_buffer[0]; // 前半段电平即该位的值 bit_count; if (bit_count 21) { rc6_frame_done(bit_buffer); rc6_stop_sampling(); } } } }逻辑说明half_index从 0 开始第一次中断采样的是数据位 1 的前半段存到half_buffer[0]第二次中断采样的是同一数据位的后半段存到half_buffer[1]。填完两次后half_index回到 0此时校验两次采样值是否相反。曼彻斯特编码中合法的一位必然前半与后半相反如果相同说明信号污染或者定时器同步丢失立刻停止采样。取half_buffer[0]作为位值是因为我们定义了逻辑 1 前高后低、逻辑 0 前低后高前半段电平直接等于逻辑值。这里有读者会问如果接收头反相前半段电平不也反了吗确实但rc6_read_pin()读取的是接收头输出的实际电平我们定义曼彻斯特规则时就是按“实际读取电平”来定义的因此代码里不需要额外反相。只要引导脉冲的状态机也是用同一个电平定义前后就一致。半位采样从bit_count为 1 开始起始位已经在状态机处理时确认并当作 bit0 记入。定时器第一次中断对应数据位 1 的前半段所以代码里bit_count初始值要在rc6_start_half_timer()里设为 1。采样位数的上限是 21也就是起始位加后续 20 位。到达 21 位后触发rc6_frame_done完成一帧解码。如果中间出现非法半位对停止采样并回到空闲状态。4.3 解码结果回调与帧合法性校验rc6_frame_done把原始 21 位传给上层前一定要做基本合法性校验。RC6 模式 0 中模式段应该是 000不同协议变体可能用 001 表示扩展模式但常见遥控器都是 000。程序里提取模式段、地址、命令并把翻转位单独拿出来void rc6_frame_done(uint8_t bits[21]) { // bits[0] 起始位固定为 1 // bits[1..3] 模式段 uint8_t mode (bits[1] 2) | (bits[2] 1) | bits[3]; // bits[4] 翻转位 uint8_t toggle bits[4]; // bits[5..12] 地址 uint8_t addr 0; for (int i 0; i 8; i) { addr (addr 1) | bits[5 i]; } // bits[13..20] 命令 uint8_t cmd 0; for (int i 0; i 8; i) { cmd (cmd 1) | bits[13 i]; } if (mode ! 0) return; // 只接受模式 0 if (addr 0x00 cmd 0x00) return; // 空值防抖 rc6_key_callback(addr, cmd, toggle); }这里命令和地址的高低位顺序没有统一标准不同遥控器厂商可能把第一位作为最高位或最低位。我的做法是先从逻辑分析仪的波形上对照按键功能表确定顺序再决定左移还是右移如果方向反了解码出来的 cmd 会和按键表呈镜像关系。rc6_key_callback是留给应用层的回调比如点亮指示灯、执行联动动作。中断或定时器回调里尽量只置位标志把真正的业务逻辑放到主循环。注意如果地址和命令全 0绝大多数真实遥控器不会产生这种值可以用来丢弃空包。5. RC6解码常见问题排查五条实战避坑记录状态机跑起来之后真正的挑战才刚开始。RC6 解码在实木桌、白炽灯、空调遥控器旁边都可能出现各种稀奇古怪的问题。这里整理五条我在实际项目里踩过的坑每一条都按“现象 - 原因 - 解决”描述方便你对照排查。5.1 现象偶尔解出一帧下一帧完全没反应现象是按键十次只有两三次能读到键值其余时间逻辑分析仪看波形正常程序就是不出来。最开始怀疑是中断优先级问题查了一圈发现是容差阈值写死了 T_TICKS而那块遥控器的实际 T 是 460us差 3.6% 不算大但引导脉冲 6T 差 96us在 25% 容差内其实没问题。真正原因是定时器和逻辑分析仪的地参考不同测量时忘了把接收头的地和单片机共地。接收头输出波形在没有共地时看起来正常但 ADC 比较器抖动导致边沿时间戳误差时好时坏。解决手段很笨把接收头 GND 和单片机 GND 用最短的线连接并在接收头旁边补了一颗 0.1uF 电容。之后解码成功率恢复到近乎 100%。如果硬件没问题再检查状态机 IDLE 状态是否在每次采样停止后正确复位有时半位定时器停晚了把下一帧引导脉冲采样进去也会表现为无响应。5.2 现象按住按键时第一键正常后续全是乱码红外遥控器在按键持续期间会重复发送同一帧间隔在几十毫秒到一百多毫秒不等。问题出在解码器收到第一帧后进入空闲但没有等待静默期。第二帧的引导脉冲到来时半位采样定时器可能还残留中断或者状态机里last_edge_time还是上一帧的时间导致引导高段宽度计算包含了帧间空白等于超出了 6T 容差。解决方法是每帧结束成功或失败后强制关断半位采样并让状态机进入一个“帧间隔禁止窗口”。最简单的方式是记录帧结束时间下一次边沿到来时先判断距离上次帧结束是否超过 10T不超过就丢弃。这样能避免把重复帧的尾巴当作新帧头。同时要把last_edge_time清零或加上一个足够大的偏移让第一个引导脉冲宽度从真正的高段开始计时。5.3 现象距离稍远就丢帧靠近遥控器才能用排除功率问题后这是典型的接收头输出上升沿变缓。一体化接收头输出低电平时靠内部管子拉低恢复高电平只能靠上拉电阻。如果上拉电阻选的 100kΩRC 常数太大上升沿变成斜坡输入捕获模块可能检测不到上升沿或者检测时间明显滞后导致 1T 间隔测出来偏大。解决方法是把上拉电阻降到 4.7kΩ并检查主控的 GPIO 是否启用了内部上拉。有时候内部上拉并联外部上拉会变成两者并联等效阻值更小不影响功能。另一个隐藏因素是信号线上并联了太大电容比如软排线过长分布电容会让边沿更缓。把接收头到主控的连线控制在 10cm 以内不要用长杜邦线飞线。如果非要长距离用屏蔽线或加一级施密特触发器整形。5.4 现象空调遥控器在附近时解码器被反复唤醒空调遥控器多为广角发射且也会在按键时发送重复帧。RC6 状态机在 IDLE 状态下对任何上升沿都敏感其他协议的遥控器只要载波也是 38kHz就可能触发引导高段检测。假如空调遥控器的按键帧恰好有接近 6T 的高段状态机就会进入 LEADER_LOW随后因为 2T 不匹配回到 IDLE。虽然不会误报按键但主循环被频繁唤醒功耗指标难看。解决思路是增加引导脉冲的验证条件不仅要求 6T 高段还要求紧随其后是 2T 低段这两步已经很严格。更高阶的做法是在起始位之后检查模式段是否为预期值例如模式 0 必须是三个 0。如果收到 3 位非 000 的模式说明协议不匹配直接丢弃。另外可以在接收头输出上并联一个 100pF 到 470pF 的小电容滤除高频毛刺但电容太大会吃掉边沿必须实测。5.5 现象接入RTOS后解码偶发卡死复位后恢复卡死表现为系统其他任务正常但红外键值再也进不来。检查后发现半位采样中断和输入捕获中断共享同一个定时器外设而 RTOS 的某个驱动也配置了定时器通道抢占导致寄存器被覆盖。解决方法是明确中断优先级和资源归属输入捕获中断优先级高于半位采样定时器中断因为边沿同步一旦丢失整个帧都废半位采样中断只访问静态数组不加锁解码回调里不做任何可能阻塞的操作比如互斥锁、打印、延时这些全部放到任务里。如果项目里只有一个定时器可用就把它设计成交错模式先捕获后定时但要注意切换寄存器时关闭中断。之后我专门为红外解码保留了一个独占定时器问题再没出现过。6. 把RC6解码做成可复用的学习模块自动T值测量与发送扩展代码跑通后下一步最关键的是不要写死 T。同一品牌不同批次的遥控器 T 值可能差 10%更不用说还有 RC6 的扩展模式。我把解码器改成了支持自动学习的模块每次进入 IDLE 后用第一条引导脉冲高段时间除以 6作为当前遥控器的 T 估计值。这样同一台设备可以适配多种 RC6 遥控器不需要每次烧录新固件。具体实现是在检测到引导高段结束时记录高段宽度leader_high_ticks然后计算T leader_high_ticks / 6。把这个新 T 用于后续 2T、1T 的判定同时更新半位采样定时器的周期。第一次接收第一帧时不急着送按键值先学习参数第二帧再用新参数解码。这里要注意如果高段本身被干扰除出来的 T 可能离谱所以要限制 T 在 300us 到 600us 之间超出范围就丢弃。发送功能扩展也不难红外发射电路用 38kHz 载波按 RC6 的帧结构反向生成波形。发送前先输出 6T 载波再停 2T然后按曼彻斯特规则逐位发送。用 PWM 产生载波普通 GPIO 控制载波通断就可以实现。难点在于精确的 1T 定时和接收一样建议用定时器不要用软件延时循环。我最早用delay_us写发送结果每发一次系统时间基准就漂一点后来换成 DMA 加定时器波形输出才稳定。我给自己的硬性要求是接收模块必须在没有逻辑分析仪的情况下也能自适应参数发送模块必须能回读自己发射的波形。如果你做万能遥控器这两条是底线。最深的教训是RC6 协议看似小众一旦环境里有同频红外干扰任何“看起来对”的脉冲序列都可能是伪帧所以合法帧校验永远不能省。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?