Android 红外功能实现与红外协议开发笔记:ConsumerIrManager、NEC、PWM、曼彻斯特编码

Android 红外功能实现与红外协议开发笔记:ConsumerIrManager、NEC、PWM、曼彻斯特编码

Android 从 API 19 开始提供 ConsumerIrManager,带有消费级红外发射器的设备可以直接通过系统 API 发送红外信号。Android 红外功能本身并不负责识别家电型号或解析遥控协议,核心能力可以概括为:载波频率 + 红外脉冲序列 → ConsumerIrManager → 红外发射器。

ini 复制代码
consumerIrManager.transmit(carrierFrequency, pattern);

实际开发涉及的完整链路是:设备类型 + 厂商型号 + 红外协议 + 命令数据 + 协议编码 + Mark/Space 波形 + ConsumerIrManager + 红外发射。

一、红外信号基本结构

红外遥控并不是简单地点亮一次红外 LED,而是通过载波、脉冲和协议共同传递数据。完整过程可以记录为:遥控命令 → 协议编码 → Mark/Space 时序 → 载波调制 → 红外 LED → 家电接收头 → 协议解码。

常见消费级红外载波在几十 kHz 范围内,NEC 常见载波为 38kHz。

ini 复制代码
int carrierFrequency = 38000;

这里的 38000 单位为 Hz。载波负责红外 LED 在 Mark 阶段的高速闪烁,协议负责控制 Mark 和 Space 持续多长时间。

二、Mark、Space 与 Android pattern

红外协议描述中通常使用 Mark 和 Space。Mark = 发射载波,Space = 停止发射载波。

例如:

css 复制代码
9000μs Mark + 4500μs Space + 560μs Mark + 560μs Space

对应 Android:

ini 复制代码
int[] pattern = {
    9000, 4500,
    560, 560
};

Android 的 pattern 按 Mark + Space + Mark + Space... 交替排列,单位为微秒 μs。因此 9000, 4500, 560, 560 表示:发射 9000μs + 停止 4500μs + 发射 560μs + 停止 560μs。

需要注意"高电平、低电平"的说法依赖波形观察位置以及接收头是否反相。协议分析时使用 Mark / Space 更准确。下面为了方便观察波形,在约定 Mark = 高、Space = 低 的情况下描述高低电平。

三、常见红外编码方式

红外遥控中经常遇到的编码方式包括:脉冲距离编码 PDM + 脉宽编码 PWM + 曼彻斯特编码 Manchester/Biphase。

NEC:脉冲距离编码

NEC 使用 Pulse Distance Modulation,也就是脉冲距离编码。每个数据位的 Mark 长度基本固定,区别主要体现在后面的 Space 时间。

css 复制代码
逻辑 0:560μs Mark + 560μs Space
逻辑 1:560μs Mark + 1690μs Space

在约定 Mark = 高电平 的逻辑波形中可以记录为:NEC 无论发送 0 还是 1,高电平持续时间基本固定,区别在于后面的低电平时间长短。

因此在逻辑分析仪或已经解调后的波形中,如果看到:一串宽度基本一致的短脉冲 + 脉冲之间的间隔一短一长,很符合 NEC/PDM 一类协议的特征。

NEC 的逻辑 0 总时长约 1.12ms,逻辑 1 总时长约 2.25ms,常见载波为 38kHz。

PWM:脉宽编码

PWM 即 Pulse Width Modulation,数据通过脉冲本身的宽度变化表达。

在约定 有效脉冲 = 高电平 的波形中,可以记录为:低电平间隔时间固定,区别在于高电平持续时间长短。

波形看起来更像:不同宽度的实心方块 + 基本固定的间隔。

三种编码观察特征可以先记录为:NEC/PDM:Mark 固定 + Space 变化,PWM:Space 固定 + Mark 变化。

曼彻斯特编码

曼彻斯特编码不依赖简单的"长脉冲"和"短脉冲"表达 0、1,而是把每一个 bit 划分成两个半 bit,并要求 bit 中间发生一次电平翻转。

可以记录为:一个 Bit → 前半周期 + 后半周期 → 中间必须发生一次翻转。

逻辑 0 和逻辑 1 通过翻转方向区分。究竟是 高→低 = 0 还是 低→高 = 0 取决于协议定义。

RC5 就属于典型的 Manchester/Biphase 编码协议。

波形特征可以记录为:Bit 周期固定 + 每个 bit 中间都有翻转 + 连续 bit 边界可能再次发生翻转。

三类编码可以压缩为:NEC/PDM:Mark 固定 + Space 变化,PWM:Space 固定 + Mark 变化,Manchester:Bit 周期固定 + Bit 中点必须翻转。

四、NEC 标准协议

NEC 是消费级红外遥控中很常见的一类协议,大量遥控器以及厂商衍生协议可以看到 NEC/PDM 的结构特征,但不能只根据 TCL、创维、海尔、美的等品牌直接断定某个具体型号一定使用标准 NEC。

标准 NEC 的核心参数可以记录为:38kHz 载波 + 9ms 引导 Mark + 4.5ms 引导 Space + 32bit 数据 + 560μs 结束 Mark。

完整标准帧:

css 复制代码
9ms Mark + 4.5ms Space + Address 8bit + ~Address 8bit + Command 8bit + ~Command 8bit + 560μs Mark

也可以写成:引导码 + 地址码 + 地址反码 + 命令码 + 命令反码 + 结束脉冲。

其中真正的数据部分是:Address 8bit + ~Address 8bit + Command 8bit + ~Command 8bit = 32bit。

五、标准 NEC 数据包结构

以:

复制代码
0x00FF02FD

为例,32 位数据可以拆成:

复制代码
00 FF 02 FD

完整 NEC 帧可以表示为:

css 复制代码
9000μs Mark + 4500μs Space + 0x00 + 0xFF + 0x02 + 0xFD + 560μs Mark

1. 引导码

9000μs Mark + 4500μs Space,用于标识一帧数据开始,同时给接收端留出增益调整和准备接收数据的时间。

2. 地址码

复制代码
0x00

8 位地址,也经常叫 Address / Customer Code,用于区分设备、系统或者一组遥控目标。

0x00 本身不能固定解释成电视机,不同厂家和设备可以使用不同地址。

3. 地址反码

复制代码
0xFF

标准 NEC 会将地址取反后再次发送:

复制代码
0x00 → 0xFF

接收端可以检查:

ini 复制代码
address ^ addressInverse == 0xFF

如果结果不满足,可以认为数据发生错误。

4. 命令码

复制代码
0x02

8 位命令数据,对应当前遥控器定义的具体动作,例如电源、音量、频道等。

0x02 具体代表什么由设备协议决定,不能脱离对应设备码表解释。

5. 命令反码

复制代码
0xFD

命令同样发送反码:

复制代码
0x02 → 0xFD

可以检查:

ini 复制代码
command ^ commandInverse == 0xFF

因此:

ini 复制代码
00 ^ FF = FF
02 ^ FD = FF

标准 NEC 通过:原始数据 + 反码 的方式提供基础校验。

六、NEC Bit 顺序

NEC 每个字节采用 LSB First,也就是最低位优先发送。

例如:

ini 复制代码
0x02 = 00000010

通常书写顺序:

复制代码
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
 0    0    0    0    0    0    1    0

实际发送顺序:

复制代码
bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7
 0    1    0    0    0    0    0    0

Java 编码时直接从最低位开始读取:

arduino 复制代码
for (int bit = 0; bit < 8; bit++) {
    boolean one = (value & (1 << bit)) != 0;
}

七、NEC 数据转换成 Android pattern

NEC 基本时序:

arduino 复制代码
private static final int HEADER_MARK = 9000;
private static final int HEADER_SPACE = 4500;
private static final int BIT_MARK = 560;
private static final int ZERO_SPACE = 560;
private static final int ONE_SPACE = 1690;
private static final int END_MARK = 560;

完整转换代码:

ini 复制代码
int address = 0x00;
int command = 0x02;

int[] data = {
    address,
    address ^ 0xFF,
    command,
    command ^ 0xFF
};

List<Integer> values = new ArrayList<>();

values.add(9000);
values.add(4500);

for (int value : data) {
    for (int bit = 0; bit < 8; bit++) {
        boolean one = (value & (1 << bit)) != 0;

        values.add(560);
        values.add(one ? 1690 : 560);
    }
}

values.add(560);

int[] pattern = new int[values.size()];

for (int i = 0; i < values.size(); i++) {
    pattern[i] = values.get(i);
}

生成的数据结构为:9000,4500 + 32 × (560,560/1690) + 560。

八、NEC 重复码

NEC 在按键持续按住时,可以使用 Repeat Code,而不是反复发送完整 32bit 数据。

标准重复码:

css 复制代码
9000μs Mark + 2250μs Space + 560μs Mark

Android 波形:

ini 复制代码
int[] repeatPattern = {
    9000,
    2250,
    560
};

长按音量、频道等按键时,需要同时考虑:首次完整帧 + Repeat Code + 重复间隔。

九、Android 红外权限

AndroidManifest.xml:

ini 复制代码
<uses-permission android:name="android.permission.TRANSMIT_IR" />

<uses-feature
    android:name="android.hardware.consumerir"
    android:required="false" />

TRANSMIT_IR 属于普通权限,不需要运行时弹窗申请。

android.hardware.consumerir 设置为 required="false" 后,没有红外发射器的设备仍然可以安装应用。

十、检测红外硬件

ini 复制代码
boolean hasFeature = getPackageManager().hasSystemFeature(
        PackageManager.FEATURE_CONSUMER_IR
);

ConsumerIrManager consumerIrManager =
        (ConsumerIrManager) getSystemService(
                Context.CONSUMER_IR_SERVICE
        );

boolean supported =
        hasFeature
        && consumerIrManager != null
        && consumerIrManager.hasIrEmitter();

设备检测可以保持:FEATURE_CONSUMER_IR + ConsumerIrManager + hasIrEmitter()。

十一、直接发送红外

已有 pattern 后直接调用:

ini 复制代码
ConsumerIrManager consumerIrManager =
        (ConsumerIrManager) getSystemService(
                Context.CONSUMER_IR_SERVICE
        );

int carrierFrequency = 38000;

int[] pattern = {
    9000, 4500,
    560, 560,
    560, 1690,
    560, 560
};

if (consumerIrManager != null && consumerIrManager.hasIrEmitter()) {
    consumerIrManager.transmit(
            carrierFrequency,
            pattern
    );
}

NEC 数据完整链路为:Address + Command → NEC 编码 → Mark/Space pattern → 38kHz → ConsumerIrManager.transmit()。

十二、载波范围

Android 可以读取当前设备红外硬件支持的载波范围:

scss 复制代码
ConsumerIrManager consumerIrManager =
        (ConsumerIrManager) getSystemService(
                Context.CONSUMER_IR_SERVICE
        );

ConsumerIrManager.CarrierFrequencyRange[] ranges =
        consumerIrManager.getCarrierFrequencies();

if (ranges != null) {
    for (ConsumerIrManager.CarrierFrequencyRange range : ranges) {
        Log.d(
                "IR",
                range.getMinFrequency()
                        + "Hz ~ "
                        + range.getMaxFrequency()
                        + "Hz"
        );
    }
}

码库至少需要保存:协议 + 载波频率 + 数据或波形。

十三、红外数据类型

实际收集码库时,经常会遇到:协议地址/命令 + HEX 数据 + Raw Timings + Pronto Hex + 载波周期计数。

Android ConsumerIrManager.transmit() 最终需要的是:

ini 复制代码
int carrierFrequency;
int[] pattern;

例如:

ini 复制代码
int carrierFrequency = 38000;

int[] pattern = {
    9000, 4500,
    560, 560,
    560, 1690,
    560, 560
};

数据处理链路可以记录为:原始红外数据 → 协议识别 → 格式转换 → Mark/Space → Android pattern。

十四、空调状态编码

电视、机顶盒一类遥控器经常使用离散命令:电源 + 音量+ + 音量- + 频道+ + 频道-。

空调协议中大量设备使用完整状态帧,调整温度时可能重新发送:开关状态 + 工作模式 + 温度 + 风速 + 扫风 + 定时 + 其他状态。

因此空调红外开发的重点不只是按键码,而是:当前状态 → 厂商协议编码 → 完整状态帧 → Mark/Space → ConsumerIrManager。

例如当前状态可以是:

复制代码
开机 + 制冷 + 26℃ + 自动风 + 扫风关闭

修改为 27℃ 后,实际发送的可能是:

复制代码
开机 + 制冷 + 27℃ + 自动风 + 扫风关闭

而不是单独发送一条"温度 +1"。

相关推荐
千里马学框架6 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台6 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone6 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc6 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo6 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077006 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼6 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone6 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen6 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone6 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui