电脑通过HDMI反向控制外接显示器亮度和声音——DDC/CI与CEC协议全解析

前言

很多人不知道,电脑和显示器之间除了传输图像信号,还有一条"隐藏"的双向通信通道。通过这条通道,电脑可以:

  • 读取/调节显示器亮度、对比度
  • 切换输入源
  • 控制显示器电源开关
  • 调节显示器内置扬声器音量
  • 获取显示器型号、序列号等信息

这背后的技术就是DDC/CICEC协议。今天把这套技术从协议原理到代码实现整理一遍。

HDMI接口通信架构

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        HDMI接口通信通道                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   HDMI接口包含多个独立通信通道:                                              │
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                                                                     │  │
│   │   TMDS通道 (单向: 电脑→显示器)                                      │  │
│   │   ════════════════════════════════════════════════════════════     │  │
│   │   - TMDS Data 0/1/2: RGB视频数据                                   │  │
│   │   - TMDS Clock: 像素时钟                                           │  │
│   │   - 带宽: 最高48Gbps (HDMI 2.1)                                    │  │
│   │                                                                     │  │
│   ├─────────────────────────────────────────────────────────────────────┤  │
│   │                                                                     │  │
│   │   DDC通道 (双向: I2C总线)                                           │  │
│   │   ════════════════════════════════════════════════════════════     │  │
│   │   - SCL (Pin 15): I2C时钟                                          │  │
│   │   - SDA (Pin 16): I2C数据                                          │  │
│   │   - 用途: EDID读取、DDC/CI控制                                      │  │
│   │   - 速率: 100kHz标准模式                                            │  │
│   │                                                                     │  │
│   ├─────────────────────────────────────────────────────────────────────┤  │
│   │                                                                     │  │
│   │   CEC通道 (双向: 单线总线)                                          │  │
│   │   ════════════════════════════════════════════════════════════     │  │
│   │   - CEC (Pin 13): Consumer Electronics Control                     │  │
│   │   - 用途: 设备间控制 (电源、音量、输入切换)                          │  │
│   │   - 速率: 约400bps                                                  │  │
│   │                                                                     │  │
│   ├─────────────────────────────────────────────────────────────────────┤  │
│   │                                                                     │  │
│   │   HPD信号 (单向: 显示器→电脑)                                       │  │
│   │   ════════════════════════════════════════════════════════════     │  │
│   │   - Hot Plug Detect (Pin 19)                                       │  │
│   │   - 检测显示器连接/断开                                             │  │
│   │                                                                     │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   HDMI Type-A 引脚定义:                                                     │
│                                                                             │
│   ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│   │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │10 │11 │12 │13 │14 │15 │16 │17 │18 │19 │
│   └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
│     │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │
│     │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   └─ HPD
│     │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   └─ +5V
│     │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   └─ GND
│     │   │   │   │   │   │   │   │   │   │   │   │   │   │   └─ SDA (DDC)
│     │   │   │   │   │   │   │   │   │   │   │   │   │   └─ SCL (DDC)
│     │   │   │   │   │   │   │   │   │   │   │   │   └─ CEC ← 控制通道
│     │   │   │   │   │   │   │   │   │   │   │   └─ TMDS Clock-
│     │   │   │   │   │   │   │   │   │   │   └─ TMDS Clock Shield
│     │   │   │   │   │   │   │   │   │   └─ TMDS Clock+
│     └───┴───┴───┴───┴───┴───┴───┴───┴───┴─ TMDS Data 0/1/2 (+/-/Shield)
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

DDC/CI协议详解

DDC/CI概述

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        DDC/CI协议架构                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   DDC = Display Data Channel (显示数据通道)                                  │
│   CI = Command Interface (命令接口)                                         │
│                                                                             │
│   DDC协议族:                                                                │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                                                                     │  │
│   │   DDC1: 最早版本,仅读取EDID                                        │  │
│   │         └→ 显示器在SCL线上持续输出EDID数据                          │  │
│   │                                                                     │  │
│   │   DDC2B: 使用I2C协议读取EDID                                        │  │
│   │          └→ 从机地址: 0x50 (EDID EEPROM)                           │  │
│   │                                                                     │  │
│   │   DDC2Bi: DDC2B + 可选的双向通信                                    │  │
│   │                                                                     │  │
│   │   DDC/CI: 完整的双向命令接口 ← 我们关注的重点                        │  │
│   │           └→ 从机地址: 0x37 (DDC/CI控制)                           │  │
│   │           └→ 基于MCCS标准 (Monitor Control Command Set)            │  │
│   │                                                                     │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│   通信模型:                                                                 │
│                                                                             │
│   ┌──────────┐     I2C总线      ┌──────────┐                              │
│   │          │ ───────────────→ │          │                              │
│   │   电脑   │   SCL + SDA      │  显示器  │                              │
│   │  (主机)  │ ←─────────────── │  (从机)  │                              │
│   │          │                  │          │                              │
│   └──────────┘                  └──────────┘                              │
│                                                                             │
│   I2C地址分配:                                                              │
│   - 0x50 (0xA0/0xA1): EDID数据                                             │
│   - 0x37 (0x6E/0x6F): DDC/CI控制                                           │
│   - 0x30 (0x60/0x61): 扩展显示信息                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

MCCS VCP代码

c 复制代码
/**
 * MCCS (Monitor Control Command Set) VCP代码定义
 * 
 * VCP = Virtual Control Panel (虚拟控制面板)
 * 每个控制项有唯一的VCP代码
 */

// 常用VCP代码
typedef enum {
    // 亮度/对比度控制
    VCP_BRIGHTNESS              = 0x10,     // 亮度 (0-100)
    VCP_CONTRAST                = 0x12,     // 对比度 (0-100)
    VCP_BACKLIGHT               = 0x13,     // 背光亮度
    
    // 颜色控制
    VCP_RED_GAIN                = 0x16,     // 红色增益
    VCP_GREEN_GAIN              = 0x18,     // 绿色增益
    VCP_BLUE_GAIN               = 0x1A,     // 蓝色增益
    VCP_COLOR_TEMP              = 0x14,     // 色温预设
    VCP_COLOR_TEMP_REQUEST      = 0x0B,     // 色温请求
    
    // 几何控制
    VCP_HORIZONTAL_POSITION     = 0x20,     // 水平位置
    VCP_VERTICAL_POSITION       = 0x30,     // 垂直位置
    VCP_HORIZONTAL_SIZE         = 0x22,     // 水平尺寸
    VCP_VERTICAL_SIZE           = 0x32,     // 垂直尺寸
    
    // 输入控制
    VCP_INPUT_SOURCE            = 0x60,     // 输入源选择
    VCP_AUDIO_SPEAKER_VOLUME    = 0x62,     // 扬声器音量 ← 音量控制
    VCP_AUDIO_MUTE              = 0x8D,     // 静音
    VCP_AUDIO_TREBLE            = 0x8F,     // 高音
    VCP_AUDIO_BASS              = 0x91,     // 低音
    VCP_AUDIO_BALANCE           = 0x93,     // 声道平衡
    
    // 电源控制
    VCP_POWER_MODE              = 0xD6,     // 电源模式
    
    // OSD控制
    VCP_OSD_LANGUAGE            = 0xCC,     // OSD语言
    VCP_OSD                     = 0xCA,     // OSD开关
    
    // 显示器信息
    VCP_DISPLAY_CONTROLLER_TYPE = 0xC8,     // 控制器类型
    VCP_DISPLAY_FIRMWARE_LEVEL  = 0xC9,     // 固件版本
    VCP_VERSION                 = 0xDF,     // MCCS版本
    
    // 工厂设置
    VCP_RESTORE_FACTORY_DEFAULT = 0x04,     // 恢复出厂设置
    VCP_RESTORE_FACTORY_BRIGHTNESS = 0x05,  // 恢复亮度默认
    VCP_RESTORE_FACTORY_CONTRAST = 0x06,    // 恢复对比度默认
    VCP_RESTORE_FACTORY_COLOR   = 0x08,     // 恢复颜色默认
    
    // 其他
    VCP_DEGAUSS                 = 0x01,     // 消磁 (CRT)
    VCP_NEW_CONTROL_VALUE       = 0x02,     // 新控制值标志
    VCP_SOFT_CONTROLS           = 0x03,     // 软控制
} VCPCode_t;

// 输入源定义 (VCP 0x60的值)
typedef enum {
    INPUT_VGA1                  = 0x01,
    INPUT_VGA2                  = 0x02,
    INPUT_DVI1                  = 0x03,
    INPUT_DVI2                  = 0x04,
    INPUT_COMPOSITE1            = 0x05,
    INPUT_COMPOSITE2            = 0x06,
    INPUT_SVIDEO1               = 0x07,
    INPUT_SVIDEO2               = 0x08,
    INPUT_TUNER1                = 0x09,
    INPUT_TUNER2                = 0x0A,
    INPUT_TUNER3                = 0x0B,
    INPUT_COMPONENT1            = 0x0C,
    INPUT_COMPONENT2            = 0x0D,
    INPUT_COMPONENT3            = 0x0E,
    INPUT_DP1                   = 0x0F,
    INPUT_DP2                   = 0x10,
    INPUT_HDMI1                 = 0x11,
    INPUT_HDMI2                 = 0x12,
} InputSource_t;

// 电源模式定义 (VCP 0xD6的值)
typedef enum {
    POWER_ON                    = 0x01,
    POWER_STANDBY               = 0x02,
    POWER_SUSPEND               = 0x03,
    POWER_OFF                   = 0x04,
    POWER_OFF_BUTTON            = 0x05,
} PowerMode_t;

DDC/CI通信协议

c 复制代码
/**
 * DDC/CI数据包格式
 */

/*
DDC/CI消息格式:

发送命令 (主机→显示器):
┌──────┬──────┬────────┬──────────┬──────────┬──────────┐
│ 目标 │ 源   │ 长度   │ 操作码   │ 数据     │ 校验和   │
│ 地址 │ 地址 │        │          │          │          │
├──────┼──────┼────────┼──────────┼──────────┼──────────┤
│ 0x6E │ 0x51 │ 0x80+n │ opcode   │ data...  │ XOR      │
└──────┴──────┴────────┴──────────┴──────────┴──────────┘

接收响应 (显示器→主机):
┌──────┬──────┬────────┬──────────┬──────────┬──────────┐
│ 目标 │ 源   │ 长度   │ 操作码   │ 数据     │ 校验和   │
│ 地址 │ 地址 │        │          │          │          │
├──────┼──────┼────────┼──────────┼──────────┼──────────┤
│ 0x6F │ 0x6E │ 0x80+n │ opcode   │ data...  │ XOR      │
└──────┴──────┴────────┴──────────┴──────────┴──────────┘

操作码:
- 0x01: VCP Request (读取VCP值)
- 0x02: VCP Reply (VCP值响应)
- 0x03: VCP Set (设置VCP值)
- 0x06: Timing Request
- 0x07: Timing Reply
- 0x0C: Save Current Settings
- 0xC0-0xC8: Capabilities相关

校验和: 所有字节(包括I2C地址)异或
*/

#define DDC_CI_ADDR             0x37        // DDC/CI I2C地址 (7位)
#define DDC_CI_ADDR_WRITE       0x6E        // 写地址 (8位)
#define DDC_CI_ADDR_READ        0x6F        // 读地址 (8位)
#define DDC_CI_SOURCE_ADDR      0x51        // 源地址 (主机)

// DDC/CI操作码
typedef enum {
    DDC_CMD_VCP_REQUEST         = 0x01,     // 读取VCP
    DDC_CMD_VCP_REPLY           = 0x02,     // VCP响应
    DDC_CMD_VCP_SET             = 0x03,     // 设置VCP
    DDC_CMD_TIMING_REQUEST      = 0x06,     // 时序请求
    DDC_CMD_TIMING_REPLY        = 0x07,     // 时序响应
    DDC_CMD_VCP_RESET           = 0x09,     // VCP复位
    DDC_CMD_SAVE_SETTINGS       = 0x0C,     // 保存设置
    DDC_CMD_CAPABILITIES_REQUEST = 0xF3,    // 能力请求
    DDC_CMD_CAPABILITIES_REPLY  = 0xE3,     // 能力响应
} DDCCommand_t;

// DDC/CI消息结构
typedef struct {
    uint8_t dest_addr;
    uint8_t source_addr;
    uint8_t length;             // 0x80 | data_length
    uint8_t opcode;
    uint8_t data[32];
    uint8_t checksum;
} DDCMessage_t;

/**
 * 计算DDC/CI校验和
 */
uint8_t ddc_calc_checksum(const uint8_t *data, int len, uint8_t init)
{
    uint8_t checksum = init;
    
    for (int i = 0; i < len; i++) {
        checksum ^= data[i];
    }
    
    return checksum;
}

/**
 * 构建VCP读取命令
 */
int ddc_build_vcp_request(uint8_t *buffer, uint8_t vcp_code)
{
    // 格式: [dest] [source] [length] [opcode] [vcp_code] [checksum]
    buffer[0] = DDC_CI_ADDR_WRITE;   // 0x6E
    buffer[1] = DDC_CI_SOURCE_ADDR;  // 0x51
    buffer[2] = 0x82;                // 0x80 | 2 (2字节数据)
    buffer[3] = DDC_CMD_VCP_REQUEST; // 0x01
    buffer[4] = vcp_code;
    
    // 校验和: 所有字节异或 (从dest_addr开始)
    buffer[5] = ddc_calc_checksum(buffer, 5, DDC_CI_ADDR_WRITE ^ DDC_CI_SOURCE_ADDR);
    
    return 6;
}

/**
 * 构建VCP设置命令
 */
int ddc_build_vcp_set(uint8_t *buffer, uint8_t vcp_code, uint16_t value)
{
    // 格式: [dest] [source] [length] [opcode] [vcp_code] [value_h] [value_l] [checksum]
    buffer[0] = DDC_CI_ADDR_WRITE;
    buffer[1] = DDC_CI_SOURCE_ADDR;
    buffer[2] = 0x84;                // 0x80 | 4
    buffer[3] = DDC_CMD_VCP_SET;     // 0x03
    buffer[4] = vcp_code;
    buffer[5] = (value >> 8) & 0xFF; // 高字节
    buffer[6] = value & 0xFF;        // 低字节
    
    buffer[7] = ddc_calc_checksum(buffer, 7, DDC_CI_ADDR_WRITE ^ DDC_CI_SOURCE_ADDR);
    
    return 8;
}

/**
 * 解析VCP响应
 */
int ddc_parse_vcp_reply(const uint8_t *buffer, int len,
                        uint8_t *vcp_code, uint16_t *current, uint16_t *maximum)
{
    // 响应格式: [source] [length] [opcode] [result] [vcp_code] [type] [max_h] [max_l] [cur_h] [cur_l] [checksum]
    
    if (len < 10) return -1;
    
    if (buffer[0] != 0x6E) return -2;          // 源地址错误
    if ((buffer[1] & 0x80) == 0) return -3;    // 长度格式错误
    if (buffer[2] != DDC_CMD_VCP_REPLY) return -4;  // 操作码错误
    
    uint8_t result = buffer[3];
    if (result != 0x00) return -5;  // 操作失败
    
    *vcp_code = buffer[4];
    // buffer[5] = type (0x00=set/get, 0x01=momentary)
    *maximum = (buffer[6] << 8) | buffer[7];
    *current = (buffer[8] << 8) | buffer[9];
    
    return 0;
}

DDC/CI完整实现

c 复制代码
/**
 * DDC/CI驱动实现
 */

#include <stdint.h>
#include <string.h>

// I2C句柄 (平台相关)
typedef void* I2CHandle_t;

// DDC/CI设备结构
typedef struct {
    I2CHandle_t i2c;
    uint8_t bus_number;         // I2C总线号
    uint8_t is_initialized;
    
    // 缓存的显示器信息
    char manufacturer[16];
    char model[32];
    uint16_t mccs_version;
} DDCDevice_t;

static DDCDevice_t ddc_device;

/**
 * 初始化DDC/CI通信
 */
int ddc_init(int i2c_bus)
{
    ddc_device.bus_number = i2c_bus;
    
    // 打开I2C设备
    char dev_path[32];
    snprintf(dev_path, sizeof(dev_path), "/dev/i2c-%d", i2c_bus);
    
    ddc_device.i2c = i2c_open(dev_path);
    if (ddc_device.i2c == NULL) {
        return -1;
    }
    
    ddc_device.is_initialized = 1;
    
    return 0;
}

/**
 * I2C发送数据 (平台相关实现)
 */
int i2c_write(I2CHandle_t handle, uint8_t addr, const uint8_t *data, int len)
{
#ifdef __linux__
    // Linux实现
    struct i2c_msg msg = {
        .addr = addr,
        .flags = 0,
        .len = len,
        .buf = (uint8_t*)data
    };
    
    struct i2c_rdwr_ioctl_data ioctl_data = {
        .msgs = &msg,
        .nmsgs = 1
    };
    
    return ioctl((int)(intptr_t)handle, I2C_RDWR, &ioctl_data);
#else
    // 其他平台实现
    return HAL_I2C_Master_Transmit(handle, addr << 1, data, len, 100);
#endif
}

/**
 * I2C接收数据 (平台相关实现)
 */
int i2c_read(I2CHandle_t handle, uint8_t addr, uint8_t *data, int len)
{
#ifdef __linux__
    struct i2c_msg msg = {
        .addr = addr,
        .flags = I2C_M_RD,
        .len = len,
        .buf = data
    };
    
    struct i2c_rdwr_ioctl_data ioctl_data = {
        .msgs = &msg,
        .nmsgs = 1
    };
    
    return ioctl((int)(intptr_t)handle, I2C_RDWR, &ioctl_data);
#else
    return HAL_I2C_Master_Receive(handle, addr << 1, data, len, 100);
#endif
}

/**
 * 发送DDC/CI命令并接收响应
 */
int ddc_transact(const uint8_t *cmd, int cmd_len, uint8_t *response, int *resp_len)
{
    int ret;
    
    // 发送命令 (跳过第一个字节,那是I2C地址)
    ret = i2c_write(ddc_device.i2c, DDC_CI_ADDR, cmd + 1, cmd_len - 1);
    if (ret < 0) {
        return -1;
    }
    
    // 延时等待显示器处理 (DDC/CI规范要求至少40ms)
    usleep(50000);  // 50ms
    
    // 读取响应
    uint8_t temp[64];
    ret = i2c_read(ddc_device.i2c, DDC_CI_ADDR, temp, 64);
    if (ret < 0) {
        return -2;
    }
    
    // 解析响应长度
    int data_len = temp[1] & 0x7F;
    *resp_len = data_len + 3;  // source + length + data + checksum
    
    memcpy(response, temp, *resp_len);
    
    // 验证校验和
    uint8_t calc_checksum = ddc_calc_checksum(response, *resp_len - 1, 
                                               DDC_CI_ADDR_READ);
    if (calc_checksum != response[*resp_len - 1]) {
        return -3;  // 校验和错误
    }
    
    return 0;
}

/**
 * 读取VCP值
 */
int ddc_get_vcp(uint8_t vcp_code, uint16_t *current, uint16_t *maximum)
{
    uint8_t cmd[16];
    uint8_t response[64];
    int cmd_len, resp_len;
    
    // 构建命令
    cmd_len = ddc_build_vcp_request(cmd, vcp_code);
    
    // 发送并接收
    int ret = ddc_transact(cmd, cmd_len, response, &resp_len);
    if (ret < 0) {
        return ret;
    }
    
    // 解析响应
    uint8_t reply_vcp;
    ret = ddc_parse_vcp_reply(response, resp_len, &reply_vcp, current, maximum);
    if (ret < 0) {
        return ret;
    }
    
    if (reply_vcp != vcp_code) {
        return -10;  // VCP代码不匹配
    }
    
    return 0;
}

/**
 * 设置VCP值
 */
int ddc_set_vcp(uint8_t vcp_code, uint16_t value)
{
    uint8_t cmd[16];
    int cmd_len;
    
    // 构建命令
    cmd_len = ddc_build_vcp_set(cmd, vcp_code, value);
    
    // 发送命令 (设置命令通常没有响应)
    int ret = i2c_write(ddc_device.i2c, DDC_CI_ADDR, cmd + 1, cmd_len - 1);
    if (ret < 0) {
        return ret;
    }
    
    // 等待设置生效
    usleep(50000);
    
    return 0;
}

/* ========== 高级封装函数 ========== */

/**
 * 获取显示器亮度
 */
int ddc_get_brightness(uint8_t *brightness, uint8_t *max_brightness)
{
    uint16_t current, maximum;
    
    int ret = ddc_get_vcp(VCP_BRIGHTNESS, &current, &maximum);
    if (ret < 0) return ret;
    
    *brightness = (uint8_t)current;
    if (max_brightness) {
        *max_brightness = (uint8_t)maximum;
    }
    
    return 0;
}

/**
 * 设置显示器亮度
 */
int ddc_set_brightness(uint8_t brightness)
{
    return ddc_set_vcp(VCP_BRIGHTNESS, brightness);
}

/**
 * 获取显示器对比度
 */
int ddc_get_contrast(uint8_t *contrast, uint8_t *max_contrast)
{
    uint16_t current, maximum;
    
    int ret = ddc_get_vcp(VCP_CONTRAST, &current, &maximum);
    if (ret < 0) return ret;
    
    *contrast = (uint8_t)current;
    if (max_contrast) {
        *max_contrast = (uint8_t)maximum;
    }
    
    return 0;
}

/**
 * 设置显示器对比度
 */
int ddc_set_contrast(uint8_t contrast)
{
    return ddc_set_vcp(VCP_CONTRAST, contrast);
}

/**
 * 获取扬声器音量
 */
int ddc_get_volume(uint8_t *volume, uint8_t *max_volume)
{
    uint16_t current, maximum;
    
    int ret = ddc_get_vcp(VCP_AUDIO_SPEAKER_VOLUME, &current, &maximum);
    if (ret < 0) return ret;
    
    *volume = (uint8_t)current;
    if (max_volume) {
        *max_volume = (uint8_t)maximum;
    }
    
    return 0;
}

/**
 * 设置扬声器音量
 */
int ddc_set_volume(uint8_t volume)
{
    return ddc_set_vcp(VCP_AUDIO_SPEAKER_VOLUME, volume);
}

/**
 * 设置静音
 */
int ddc_set_mute(uint8_t mute)
{
    // 0x01 = 静音, 0x02 = 取消静音
    return ddc_set_vcp(VCP_AUDIO_MUTE, mute ? 0x01 : 0x02);
}

/**
 * 切换输入源
 */
int ddc_set_input_source(InputSource_t source)
{
    return ddc_set_vcp(VCP_INPUT_SOURCE, source);
}

/**
 * 获取当前输入源
 */
int ddc_get_input_source(InputSource_t *source)
{
    uint16_t current, maximum;
    
    int ret = ddc_get_vcp(VCP_INPUT_SOURCE, &current, &maximum);
    if (ret < 0) return ret;
    
    *source = (InputSource_t)current;
    
    return 0;
}

/**
 * 设置电源模式
 */
int ddc_set_power_mode(PowerMode_t mode)
{
    return ddc_set_vcp(VCP_POWER_MODE, mode);
}

/**
 * 恢复出厂设置
 */
int ddc_restore_factory_defaults(void)
{
    return ddc_set_vcp(VCP_RESTORE_FACTORY_DEFAULT, 0x01);
}

读取显示器能力

c 复制代码
/**
 * 读取显示器能力字符串
 * 
 * 能力字符串描述了显示器支持的所有VCP代码及其取值范围
 */

/**
 * 读取能力字符串
 */
int ddc_get_capabilities(char *caps_string, int max_len)
{
    uint8_t cmd[16];
    uint8_t response[64];
    int resp_len;
    int offset = 0;
    int total_len = 0;
    
    // 能力字符串可能很长,需要分块读取
    while (1) {
        // 构建能力请求命令
        cmd[0] = DDC_CI_ADDR_WRITE;
        cmd[1] = DDC_CI_SOURCE_ADDR;
        cmd[2] = 0x83;  // 长度 = 3
        cmd[3] = DDC_CMD_CAPABILITIES_REQUEST;  // 0xF3
        cmd[4] = (offset >> 8) & 0xFF;
        cmd[5] = offset & 0xFF;
        cmd[6] = ddc_calc_checksum(cmd, 6, DDC_CI_ADDR_WRITE ^ DDC_CI_SOURCE_ADDR);
        
        int ret = ddc_transact(cmd, 7, response, &resp_len);
        if (ret < 0) {
            return ret;
        }
        
        // 检查响应
        if (response[2] != DDC_CMD_CAPABILITIES_REPLY) {
            break;
        }
        
        // 提取数据
        int data_len = (response[1] & 0x7F) - 3;  // 减去offset和opcode
        
        if (data_len <= 0) {
            break;
        }
        
        // 复制数据
        int copy_len = data_len;
        if (total_len + copy_len >= max_len - 1) {
            copy_len = max_len - 1 - total_len;
        }
        
        memcpy(caps_string + total_len, response + 6, copy_len);
        total_len += copy_len;
        offset += data_len;
        
        // 检查是否结束
        if (data_len < 32) {
            break;
        }
    }
    
    caps_string[total_len] = '\0';
    
    return total_len;
}

/**
 * 解析能力字符串
 * 
 * 能力字符串格式示例:
 * (prot(monitor)type(lcd)model(Dell U2412M)cmds(01 02 03 07 0C)
 *  vcp(02 04 05 08 10 12 14(05 08 0B) 16 18 1A 60(0F 10 11 12))
 *  mswhql(1)asset_eep(32)mccs_ver(2.1))
 */

typedef struct {
    uint8_t vcp_code;
    uint8_t num_values;
    uint8_t values[32];     // 预设值列表
    uint16_t min_value;
    uint16_t max_value;
} VCPCapability_t;

typedef struct {
    char model[64];
    char type[16];
    uint16_t mccs_version;
    int num_vcp;
    VCPCapability_t vcp_caps[64];
} MonitorCapabilities_t;

/**
 * 简单的能力字符串解析
 */
int parse_capabilities(const char *caps_string, MonitorCapabilities_t *caps)
{
    memset(caps, 0, sizeof(MonitorCapabilities_t));
    
    const char *ptr = caps_string;
    
    // 查找model
    const char *model_start = strstr(ptr, "model(");
    if (model_start) {
        model_start += 6;
        const char *model_end = strchr(model_start, ')');
        if (model_end) {
            int len = model_end - model_start;
            if (len > 63) len = 63;
            strncpy(caps->model, model_start, len);
        }
    }
    
    // 查找type
    const char *type_start = strstr(ptr, "type(");
    if (type_start) {
        type_start += 5;
        const char *type_end = strchr(type_start, ')');
        if (type_end) {
            int len = type_end - type_start;
            if (len > 15) len = 15;
            strncpy(caps->type, type_start, len);
        }
    }
    
    // 查找mccs_ver
    const char *ver_start = strstr(ptr, "mccs_ver(");
    if (ver_start) {
        ver_start += 9;
        int major, minor;
        if (sscanf(ver_start, "%d.%d", &major, &minor) == 2) {
            caps->mccs_version = (major << 8) | minor;
        }
    }
    
    // 解析VCP代码列表
    const char *vcp_start = strstr(ptr, "vcp(");
    if (vcp_start) {
        vcp_start += 4;
        
        while (*vcp_start && *vcp_start != ')') {
            // 跳过空格
            while (*vcp_start == ' ') vcp_start++;
            
            // 读取VCP代码
            unsigned int vcp_code;
            if (sscanf(vcp_start, "%x", &vcp_code) != 1) {
                break;
            }
            
            VCPCapability_t *vcp = &caps->vcp_caps[caps->num_vcp++];
            vcp->vcp_code = vcp_code;
            
            // 移动到下一个
            while (*vcp_start && *vcp_start != ' ' && *vcp_start != '(' && *vcp_start != ')') {
                vcp_start++;
            }
            
            // 检查是否有预设值列表
            if (*vcp_start == '(') {
                vcp_start++;
                while (*vcp_start && *vcp_start != ')') {
                    while (*vcp_start == ' ') vcp_start++;
                    
                    unsigned int value;
                    if (sscanf(vcp_start, "%x", &value) == 1) {
                        vcp->values[vcp->num_values++] = value;
                    }
                    
                    while (*vcp_start && *vcp_start != ' ' && *vcp_start != ')') {
                        vcp_start++;
                    }
                }
                if (*vcp_start == ')') vcp_start++;
            }
            
            if (caps->num_vcp >= 64) break;
        }
    }
    
    return 0;
}

/**
 * 打印显示器能力信息
 */
void print_monitor_capabilities(const MonitorCapabilities_t *caps)
{
    printf("显示器型号: %s\n", caps->model);
    printf("显示器类型: %s\n", caps->type);
    printf("MCCS版本: %d.%d\n", caps->mccs_version >> 8, caps->mccs_version & 0xFF);
    printf("\n支持的VCP代码:\n");
    
    for (int i = 0; i < caps->num_vcp; i++) {
        VCPCapability_t *vcp = &caps->vcp_caps[i];
        
        printf("  0x%02X", vcp->vcp_code);
        
        // 打印VCP名称
        switch (vcp->vcp_code) {
        case VCP_BRIGHTNESS: printf(" (亮度)"); break;
        case VCP_CONTRAST: printf(" (对比度)"); break;
        case VCP_INPUT_SOURCE: printf(" (输入源)"); break;
        case VCP_AUDIO_SPEAKER_VOLUME: printf(" (音量)"); break;
        case VCP_POWER_MODE: printf(" (电源)"); break;
        default: break;
        }
        
        // 打印预设值
        if (vcp->num_values > 0) {
            printf(" 可选值: ");
            for (int j = 0; j < vcp->num_values; j++) {
                printf("0x%02X ", vcp->values[j]);
            }
        }
        
        printf("\n");
    }
}

CEC协议详解

CEC概述

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        HDMI-CEC协议                                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   CEC = Consumer Electronics Control (消费电子控制)                          │
│                                                                             │
│   特点:                                                                     │
│   - 单线双向总线 (Pin 13)                                                   │
│   - 低速率: ~400bps                                                         │
│   - 最多15个设备                                                            │
│   - 支持设备发现、一键播放、系统待机等                                       │
│                                                                             │
│   各厂商品牌名:                                                             │
│   - 三星: Anynet+                                                           │
│   - LG: SimpLink                                                            │
│   - Sony: BRAVIA Sync                                                       │
│   - 飞利浦: EasyLink                                                        │
│   - 东芝: Regza-Link                                                        │
│   - 松下: VIERA Link                                                        │
│                                                                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   CEC网络拓扑:                                                              │
│                                                                             │
│       ┌─────────┐                                                          │
│       │   TV    │ 逻辑地址: 0                                               │
│       │ (Root)  │                                                          │
│       └────┬────┘                                                          │
│            │ CEC总线                                                        │
│   ─────────┼───────────────────────────────────                            │
│            │                                                                │
│   ┌────────┼────────┬────────────┬────────────┐                            │
│   │        │        │            │            │                            │
│   ▼        ▼        ▼            ▼            ▼                            │
│ ┌───┐   ┌───┐   ┌───┐       ┌───┐       ┌───┐                             │
│ │PC │   │BD │   │STB│       │AVR│       │Game│                            │
│ │   │   │   │   │   │       │   │       │    │                            │
│ └───┘   └───┘   └───┘       └───┘       └───┘                             │
│ 地址:4   地址:4  地址:3      地址:5      地址:4                             │
│                                                                             │
│   逻辑地址分配:                                                             │
│   0: TV                      8: Playback Device 2                          │
│   1: Recording Device 1      9: Recording Device 3                         │
│   2: Recording Device 2      10: Tuner 4                                   │
│   3: Tuner 1                 11: Playback Device 3                         │
│   4: Playback Device 1       12: Reserved                                  │
│   5: Audio System            13: Reserved                                  │
│   6: Tuner 2                 14: Free Use                                  │
│   7: Tuner 3                 15: Broadcast (所有设备)                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

CEC消息格式

c 复制代码
/**
 * CEC消息格式和操作码
 */

/*
CEC帧格式:

起始位 + 头块 + 数据块... + 结束

头块 (Header Block):
┌────────────────┬────────────────┬─────┐
│ 发起者地址(4位) │ 目标地址(4位)  │ EOM │
└────────────────┴────────────────┴─────┘

数据块 (Data Block):
┌────────────────┬─────┐
│   数据 (8位)   │ EOM │
└────────────────┴─────┘

EOM = End of Message
- 0: 后续还有数据块
- 1: 这是最后一个数据块
*/

// CEC逻辑地址
typedef enum {
    CEC_ADDR_TV                 = 0,
    CEC_ADDR_RECORDING_1        = 1,
    CEC_ADDR_RECORDING_2        = 2,
    CEC_ADDR_TUNER_1            = 3,
    CEC_ADDR_PLAYBACK_1         = 4,
    CEC_ADDR_AUDIO_SYSTEM       = 5,
    CEC_ADDR_TUNER_2            = 6,
    CEC_ADDR_TUNER_3            = 7,
    CEC_ADDR_PLAYBACK_2         = 8,
    CEC_ADDR_RECORDING_3        = 9,
    CEC_ADDR_TUNER_4            = 10,
    CEC_ADDR_PLAYBACK_3         = 11,
    CEC_ADDR_FREE_USE           = 14,
    CEC_ADDR_BROADCAST          = 15,
    CEC_ADDR_UNREGISTERED       = 15,
} CECLogicalAddr_t;

// CEC操作码
typedef enum {
    // 一键播放
    CEC_OPCODE_ACTIVE_SOURCE            = 0x82,     // 声明为活动源
    CEC_OPCODE_IMAGE_VIEW_ON            = 0x04,     // 唤醒显示器
    CEC_OPCODE_TEXT_VIEW_ON             = 0x0D,     // 唤醒显示器(文本)
    
    // 路由控制
    CEC_OPCODE_ROUTING_CHANGE           = 0x80,
    CEC_OPCODE_ROUTING_INFORMATION      = 0x81,
    CEC_OPCODE_SET_STREAM_PATH          = 0x86,
    CEC_OPCODE_INACTIVE_SOURCE          = 0x9D,
    CEC_OPCODE_REQUEST_ACTIVE_SOURCE    = 0x85,
    
    // 待机
    CEC_OPCODE_STANDBY                  = 0x36,     // 进入待机
    
    // 系统信息
    CEC_OPCODE_CEC_VERSION              = 0x9E,
    CEC_OPCODE_GET_CEC_VERSION          = 0x9F,
    CEC_OPCODE_GIVE_PHYSICAL_ADDRESS    = 0x83,
    CEC_OPCODE_REPORT_PHYSICAL_ADDRESS  = 0x84,
    CEC_OPCODE_GET_MENU_LANGUAGE        = 0x91,
    CEC_OPCODE_SET_MENU_LANGUAGE        = 0x32,
    
    // 设备OSD名称
    CEC_OPCODE_GIVE_OSD_NAME            = 0x46,
    CEC_OPCODE_SET_OSD_NAME             = 0x47,
    CEC_OPCODE_SET_OSD_STRING           = 0x64,
    
    // 设备菜单控制
    CEC_OPCODE_MENU_REQUEST             = 0x8D,
    CEC_OPCODE_MENU_STATUS              = 0x8E,
    CEC_OPCODE_USER_CONTROL_PRESSED     = 0x44,     // 遥控器按键按下
    CEC_OPCODE_USER_CONTROL_RELEASED    = 0x45,     // 遥控器按键释放
    
    // 遥控器直通
    CEC_OPCODE_GIVE_DEVICE_POWER_STATUS = 0x8F,
    CEC_OPCODE_REPORT_POWER_STATUS      = 0x90,
    
    // 系统音频控制
    CEC_OPCODE_GIVE_AUDIO_STATUS        = 0x71,
    CEC_OPCODE_REPORT_AUDIO_STATUS      = 0x7A,
    CEC_OPCODE_SET_SYSTEM_AUDIO_MODE    = 0x72,
    CEC_OPCODE_SYSTEM_AUDIO_MODE_REQUEST = 0x70,
    CEC_OPCODE_SYSTEM_AUDIO_MODE_STATUS = 0x7E,
    CEC_OPCODE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D,
    CEC_OPCODE_USER_CONTROL_PRESSED_AUDIO = 0x44,   // 音量控制
    
    // 音频速率控制
    CEC_OPCODE_SET_AUDIO_RATE           = 0x9A,
    
    // 音频返回通道 (ARC)
    CEC_OPCODE_INITIATE_ARC             = 0xC0,
    CEC_OPCODE_REPORT_ARC_INITIATED     = 0xC1,
    CEC_OPCODE_REPORT_ARC_TERMINATED    = 0xC2,
    CEC_OPCODE_REQUEST_ARC_INITIATION   = 0xC3,
    CEC_OPCODE_REQUEST_ARC_TERMINATION  = 0xC4,
    CEC_OPCODE_TERMINATE_ARC            = 0xC5,
    
    // 其他
    CEC_OPCODE_FEATURE_ABORT            = 0x00,
    CEC_OPCODE_ABORT                    = 0xFF,
    CEC_OPCODE_POLLING_MESSAGE          = 0x100,    // 特殊,仅发送头块
} CECOpcode_t;

// 用户控制按键代码 (遥控器按键)
typedef enum {
    CEC_USER_CONTROL_SELECT             = 0x00,
    CEC_USER_CONTROL_UP                 = 0x01,
    CEC_USER_CONTROL_DOWN               = 0x02,
    CEC_USER_CONTROL_LEFT               = 0x03,
    CEC_USER_CONTROL_RIGHT              = 0x04,
    CEC_USER_CONTROL_RIGHT_UP           = 0x05,
    CEC_USER_CONTROL_RIGHT_DOWN         = 0x06,
    CEC_USER_CONTROL_LEFT_UP            = 0x07,
    CEC_USER_CONTROL_LEFT_DOWN          = 0x08,
    CEC_USER_CONTROL_ROOT_MENU          = 0x09,
    CEC_USER_CONTROL_SETUP_MENU         = 0x0A,
    CEC_USER_CONTROL_CONTENTS_MENU      = 0x0B,
    CEC_USER_CONTROL_EXIT               = 0x0D,
    
    CEC_USER_CONTROL_NUMBER_0           = 0x20,
    CEC_USER_CONTROL_NUMBER_1           = 0x21,
    CEC_USER_CONTROL_NUMBER_2           = 0x22,
    CEC_USER_CONTROL_NUMBER_3           = 0x23,
    CEC_USER_CONTROL_NUMBER_4           = 0x24,
    CEC_USER_CONTROL_NUMBER_5           = 0x25,
    CEC_USER_CONTROL_NUMBER_6           = 0x26,
    CEC_USER_CONTROL_NUMBER_7           = 0x27,
    CEC_USER_CONTROL_NUMBER_8           = 0x28,
    CEC_USER_CONTROL_NUMBER_9           = 0x29,
    
    CEC_USER_CONTROL_CHANNEL_UP         = 0x30,
    CEC_USER_CONTROL_CHANNEL_DOWN       = 0x31,
    CEC_USER_CONTROL_PREVIOUS_CHANNEL   = 0x32,
    
    CEC_USER_CONTROL_VOLUME_UP          = 0x41,     // 音量+
    CEC_USER_CONTROL_VOLUME_DOWN        = 0x42,     // 音量-
    CEC_USER_CONTROL_MUTE               = 0x43,     // 静音
    
    CEC_USER_CONTROL_PLAY               = 0x44,
    CEC_USER_CONTROL_STOP               = 0x45,
    CEC_USER_CONTROL_PAUSE              = 0x46,
    CEC_USER_CONTROL_RECORD             = 0x47,
    CEC_USER_CONTROL_REWIND             = 0x48,
    CEC_USER_CONTROL_FAST_FORWARD       = 0x49,
    CEC_USER_CONTROL_EJECT              = 0x4A,
    CEC_USER_CONTROL_FORWARD            = 0x4B,
    CEC_USER_CONTROL_BACKWARD           = 0x4C,
    
    CEC_USER_CONTROL_POWER              = 0x40,
    CEC_USER_CONTROL_POWER_TOGGLE       = 0x6B,
    CEC_USER_CONTROL_POWER_OFF          = 0x6C,
    CEC_USER_CONTROL_POWER_ON           = 0x6D,
} CECUserControl_t;

// CEC消息结构
typedef struct {
    uint8_t initiator;          // 发起者地址 (4位)
    uint8_t destination;        // 目标地址 (4位)
    uint8_t opcode;
    uint8_t parameters[14];     // 最多14字节参数
    uint8_t param_length;
} CECMessage_t;

CEC控制实现

c 复制代码
/**
 * CEC控制实现
 */

#include <stdint.h>
#include <string.h>

// CEC设备状态
typedef struct {
    int fd;                         // CEC设备文件描述符
    uint8_t logical_address;        // 本机逻辑地址
    uint16_t physical_address;      // 物理地址 (如 1.0.0.0)
    char osd_name[15];              // OSD名称
    uint8_t is_active_source;       // 是否为活动源
} CECDevice_t;

static CECDevice_t cec_device;

/**
 * 打开CEC设备 (Linux)
 */
int cec_init(const char *device_path)
{
#ifdef __linux__
    // 使用cec-utils或内核CEC框架
    cec_device.fd = open(device_path ? device_path : "/dev/cec0", O_RDWR);
    if (cec_device.fd < 0) {
        return -1;
    }
    
    // 设置物理地址 (需要从EDID获取)
    cec_device.physical_address = 0x1000;  // 1.0.0.0
    
    // 注册逻辑地址
    cec_device.logical_address = CEC_ADDR_PLAYBACK_1;
    
    strncpy(cec_device.osd_name, "PC", sizeof(cec_device.osd_name));
    
    return 0;
#else
    return -1;
#endif
}

/**
 * 发送CEC消息
 */
int cec_send_message(const CECMessage_t *msg)
{
    uint8_t buffer[16];
    int len = 0;
    
    // 头块: [initiator:4][destination:4]
    buffer[len++] = (msg->initiator << 4) | msg->destination;
    
    // 操作码
    if (msg->opcode != CEC_OPCODE_POLLING_MESSAGE) {
        buffer[len++] = msg->opcode;
        
        // 参数
        for (int i = 0; i < msg->param_length; i++) {
            buffer[len++] = msg->parameters[i];
        }
    }
    
#ifdef __linux__
    // 使用Linux CEC框架发送
    struct cec_msg cec_msg;
    memset(&cec_msg, 0, sizeof(cec_msg));
    cec_msg.len = len;
    memcpy(cec_msg.msg, buffer, len);
    
    return ioctl(cec_device.fd, CEC_TRANSMIT, &cec_msg);
#else
    return -1;
#endif
}

/**
 * 接收CEC消息
 */
int cec_receive_message(CECMessage_t *msg, int timeout_ms)
{
#ifdef __linux__
    struct cec_msg cec_msg;
    memset(&cec_msg, 0, sizeof(cec_msg));
    cec_msg.timeout = timeout_ms;
    
    int ret = ioctl(cec_device.fd, CEC_RECEIVE, &cec_msg);
    if (ret < 0) {
        return ret;
    }
    
    // 解析消息
    msg->initiator = (cec_msg.msg[0] >> 4) & 0x0F;
    msg->destination = cec_msg.msg[0] & 0x0F;
    
    if (cec_msg.len > 1) {
        msg->opcode = cec_msg.msg[1];
        msg->param_length = cec_msg.len - 2;
        memcpy(msg->parameters, &cec_msg.msg[2], msg->param_length);
    } else {
        msg->opcode = CEC_OPCODE_POLLING_MESSAGE;
        msg->param_length = 0;
    }
    
    return 0;
#else
    return -1;
#endif
}

/* ========== 高级控制函数 ========== */

/**
 * 唤醒电视
 */
int cec_power_on_tv(void)
{
    CECMessage_t msg = {
        .initiator = cec_device.logical_address,
        .destination = CEC_ADDR_TV,
        .opcode = CEC_OPCODE_IMAGE_VIEW_ON,
        .param_length = 0
    };
    
    return cec_send_message(&msg);
}

/**
 * 使电视进入待机
 */
int cec_standby_tv(void)
{
    CECMessage_t msg = {
        .initiator = cec_device.logical_address,
        .destination = CEC_ADDR_TV,
        .opcode = CEC_OPCODE_STANDBY,
        .param_length = 0
    };
    
    return cec_send_message(&msg);
}

/**
 * 使所有设备进入待机
 */
int cec_standby_all(void)
{
    CECMessage_t msg = {
        .initiator = cec_device.logical_address,
        .destination = CEC_ADDR_BROADCAST,
        .opcode = CEC_OPCODE_STANDBY,
        .param_length = 0
    };
    
    return cec_send_message(&msg);
}

/**
 * 声明为活动源 (切换电视输入到本设备)
 */
int cec_set_active_source(void)
{
    CECMessage_t msg = {
        .initiator = cec_device.logical_address,
        .destination = CEC_ADDR_BROADCAST,
        .opcode = CEC_OPCODE_ACTIVE_SOURCE,
        .param_length = 2
    };
    
    // 参数: 物理地址
    msg.parameters[0] = (cec_device.physical_address >> 8) & 0xFF;
    msg.parameters[1] = cec_device.physical_address & 0xFF;
    
    cec_device.is_active_source = 1;
    
    return cec_send_message(&msg);
}

/**
 * 发送遥控器按键
 */
int cec_send_keypress(uint8_t destination, CECUserControl_t key)
{
    CECMessage_t msg = {
        .initiator = cec_device.logical_address,
        .destination = destination,
        .opcode = CEC_OPCODE_USER_CONTROL_PRESSED,
        .param_length = 1
    };
    msg.parameters[0] = key;
    
    int ret = cec_send_message(&msg);
    if (ret < 0) return ret;
    
    // 延时后发送释放
    usleep(100000);  // 100ms
    
    msg.opcode = CEC_OPCODE_USER_CONTROL_RELEASED;
    msg.param_length = 0;
    
    return cec_send_message(&msg);
}

/**
 * 音量控制 (通过音频系统)
 */
int cec_volume_up(void)
{
    return cec_send_keypress(CEC_ADDR_AUDIO_SYSTEM, CEC_USER_CONTROL_VOLUME_UP);
}

int cec_volume_down(void)
{
    return cec_send_keypress(CEC_ADDR_AUDIO_SYSTEM, CEC_USER_CONTROL_VOLUME_DOWN);
}

int cec_mute(void)
{
    return cec_send_keypress(CEC_ADDR_AUDIO_SYSTEM, CEC_USER_CONTROL_MUTE);
}

/**
 * 直接控制电视音量 (如果电视不连接音响)
 */
int cec_tv_volume_up(void)
{
    return cec_send_keypress(CEC_ADDR_TV, CEC_USER_CONTROL_VOLUME_UP);
}

int cec_tv_volume_down(void)
{
    return cec_send_keypress(CEC_ADDR_TV, CEC_USER_CONTROL_VOLUME_DOWN);
}

/**
 * 获取设备电源状态
 */
int cec_get_power_status(uint8_t destination, uint8_t *power_status)
{
    CECMessage_t msg = {
        .initiator = cec_device.logical_address,
        .destination = destination,
        .opcode = CEC_OPCODE_GIVE_DEVICE_POWER_STATUS,
        .param_length = 0
    };
    
    int ret = cec_send_message(&msg);
    if (ret < 0) return ret;
    
    // 等待响应
    CECMessage_t response;
    ret = cec_receive_message(&response, 1000);
    if (ret < 0) return ret;
    
    if (response.opcode == CEC_OPCODE_REPORT_POWER_STATUS) {
        *power_status = response.parameters[0];
        // 0x00 = On, 0x01 = Standby, 0x02 = In transition to On, 0x03 = In transition to Standby
        return 0;
    }
    
    return -1;
}

Windows/Linux实现

Windows DDC/CI实现

c 复制代码
/**
 * Windows平台DDC/CI实现
 * 
 * 使用 PhysicalMonitor API
 */

#ifdef _WIN32
#include <windows.h>
#include <highlevelmonitorconfigurationapi.h>
#include <lowlevelmonitorconfigurationapi.h>
#include <physicalmonitorenumerationapi.h>

#pragma comment(lib, "Dxva2.lib")

typedef struct {
    HMONITOR hMonitor;
    PHYSICAL_MONITOR physicalMonitor;
    DWORD numMonitors;
} WinDDCDevice_t;

static WinDDCDevice_t win_ddc;

/**
 * 初始化Windows DDC
 */
int win_ddc_init(void)
{
    // 获取主显示器
    POINT pt = {0, 0};
    win_ddc.hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
    
    // 获取物理显示器数量
    if (!GetNumberOfPhysicalMonitorsFromHMONITOR(win_ddc.hMonitor, &win_ddc.numMonitors)) {
        return -1;
    }
    
    // 获取物理显示器句柄
    if (!GetPhysicalMonitorsFromHMONITOR(win_ddc.hMonitor, 1, &win_ddc.physicalMonitor)) {
        return -2;
    }
    
    return 0;
}

/**
 * 关闭Windows DDC
 */
void win_ddc_close(void)
{
    DestroyPhysicalMonitors(1, &win_ddc.physicalMonitor);
}

/**
 * 获取亮度
 */
int win_ddc_get_brightness(DWORD *minimum, DWORD *current, DWORD *maximum)
{
    if (!GetMonitorBrightness(win_ddc.physicalMonitor.hPhysicalMonitor,
                              minimum, current, maximum)) {
        return -1;
    }
    return 0;
}

/**
 * 设置亮度
 */
int win_ddc_set_brightness(DWORD brightness)
{
    if (!SetMonitorBrightness(win_ddc.physicalMonitor.hPhysicalMonitor, brightness)) {
        return -1;
    }
    return 0;
}

/**
 * 获取对比度
 */
int win_ddc_get_contrast(DWORD *minimum, DWORD *current, DWORD *maximum)
{
    if (!GetMonitorContrast(win_ddc.physicalMonitor.hPhysicalMonitor,
                            minimum, current, maximum)) {
        return -1;
    }
    return 0;
}

/**
 * 设置对比度
 */
int win_ddc_set_contrast(DWORD contrast)
{
    if (!SetMonitorContrast(win_ddc.physicalMonitor.hPhysicalMonitor, contrast)) {
        return -1;
    }
    return 0;
}

/**
 * 使用低级API读取/设置VCP
 */
int win_ddc_get_vcp(BYTE vcp_code, DWORD *current, DWORD *maximum)
{
    MC_VCP_CODE_TYPE codeType;
    
    if (!GetVCPFeatureAndVCPFeatureReply(win_ddc.physicalMonitor.hPhysicalMonitor,
                                          vcp_code, &codeType, current, maximum)) {
        return -1;
    }
    return 0;
}

int win_ddc_set_vcp(BYTE vcp_code, DWORD value)
{
    if (!SetVCPFeature(win_ddc.physicalMonitor.hPhysicalMonitor, vcp_code, value)) {
        return -1;
    }
    return 0;
}

/**
 * 获取显示器音量 (通过VCP)
 */
int win_ddc_get_volume(DWORD *volume, DWORD *max_volume)
{
    return win_ddc_get_vcp(VCP_AUDIO_SPEAKER_VOLUME, volume, max_volume);
}

/**
 * 设置显示器音量
 */
int win_ddc_set_volume(DWORD volume)
{
    return win_ddc_set_vcp(VCP_AUDIO_SPEAKER_VOLUME, volume);
}

/**
 * 切换输入源
 */
int win_ddc_set_input(DWORD input_source)
{
    return win_ddc_set_vcp(VCP_INPUT_SOURCE, input_source);
}

#endif  // _WIN32

Linux DDC/CI实现

c 复制代码
/**
 * Linux平台DDC/CI实现
 * 
 * 使用 ddcutil 或直接访问 /dev/i2c-*
 */

#ifdef __linux__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>

/**
 * 查找DDC/CI I2C总线
 * 
 * DDC通常在I2C总线上,可能需要尝试多个总线
 */
int linux_find_ddc_bus(void)
{
    char path[32];
    
    for (int bus = 0; bus < 20; bus++) {
        snprintf(path, sizeof(path), "/dev/i2c-%d", bus);
        
        int fd = open(path, O_RDWR);
        if (fd < 0) continue;
        
        // 尝试读取EDID
        if (ioctl(fd, I2C_SLAVE, 0x50) >= 0) {
            uint8_t edid[128];
            uint8_t addr = 0;
            
            // 写入地址
            if (write(fd, &addr, 1) == 1) {
                // 读取EDID
                if (read(fd, edid, 128) == 128) {
                    // 检查EDID头
                    if (edid[0] == 0x00 && edid[1] == 0xFF && edid[7] == 0x00) {
                        close(fd);
                        printf("找到DDC总线: /dev/i2c-%d\n", bus);
                        return bus;
                    }
                }
            }
        }
        
        close(fd);
    }
    
    return -1;
}

/**
 * 使用ddcutil命令行工具 (推荐方式)
 */
int linux_ddcutil_get_brightness(int *brightness)
{
    FILE *fp = popen("ddcutil getvcp 10 --brief", "r");
    if (!fp) return -1;
    
    char line[256];
    while (fgets(line, sizeof(line), fp)) {
        // 格式: VCP 10 C 50 100
        int vcp, type, current, max;
        char type_char;
        if (sscanf(line, "VCP %x %c %d %d", &vcp, &type_char, &current, &max) == 4) {
            if (vcp == 0x10) {
                *brightness = current;
                pclose(fp);
                return 0;
            }
        }
    }
    
    pclose(fp);
    return -1;
}

int linux_ddcutil_set_brightness(int brightness)
{
    char cmd[64];
    snprintf(cmd, sizeof(cmd), "ddcutil setvcp 10 %d", brightness);
    return system(cmd);
}

int linux_ddcutil_get_volume(int *volume)
{
    FILE *fp = popen("ddcutil getvcp 62 --brief", "r");
    if (!fp) return -1;
    
    char line[256];
    while (fgets(line, sizeof(line), fp)) {
        int vcp, current, max;
        char type_char;
        if (sscanf(line, "VCP %x %c %d %d", &vcp, &type_char, &current, &max) == 4) {
            if (vcp == 0x62) {
                *volume = current;
                pclose(fp);
                return 0;
            }
        }
    }
    
    pclose(fp);
    return -1;
}

int linux_ddcutil_set_volume(int volume)
{
    char cmd[64];
    snprintf(cmd, sizeof(cmd), "ddcutil setvcp 62 %d", volume);
    return system(cmd);
}

int linux_ddcutil_set_input(int input)
{
    char cmd[64];
    snprintf(cmd, sizeof(cmd), "ddcutil setvcp 60 0x%02x", input);
    return system(cmd);
}

/**
 * 使用Linux CEC框架
 */
int linux_cec_init(void)
{
    int fd = open("/dev/cec0", O_RDWR);
    if (fd < 0) {
        perror("打开CEC设备失败");
        return -1;
    }
    
    // 获取CEC能力
    struct cec_caps caps;
    if (ioctl(fd, CEC_ADAP_G_CAPS, &caps) < 0) {
        close(fd);
        return -2;
    }
    
    printf("CEC适配器: %s\n", caps.driver);
    printf("CEC能力: 0x%08x\n", caps.capabilities);
    
    return fd;
}

#endif  // __linux__

应用示例

完整控制程序

c 复制代码
/**
 * 显示器控制程序示例
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>

void print_usage(const char *prog)
{
    printf("用法: %s [选项]\n", prog);
    printf("\n选项:\n");
    printf("  --brightness <值>    设置亮度 (0-100)\n");
    printf("  --contrast <值>      设置对比度 (0-100)\n");
    printf("  --volume <值>        设置音量 (0-100)\n");
    printf("  --mute               静音\n");
    printf("  --unmute             取消静音\n");
    printf("  --input <源>         切换输入源 (hdmi1/hdmi2/dp1/vga)\n");
    printf("  --power-on           开机\n");
    printf("  --power-off          关机\n");
    printf("  --info               显示显示器信息\n");
    printf("  --help               显示帮助\n");
}

int parse_input_source(const char *str)
{
    if (strcasecmp(str, "hdmi1") == 0) return INPUT_HDMI1;
    if (strcasecmp(str, "hdmi2") == 0) return INPUT_HDMI2;
    if (strcasecmp(str, "dp1") == 0) return INPUT_DP1;
    if (strcasecmp(str, "dp2") == 0) return INPUT_DP2;
    if (strcasecmp(str, "vga") == 0) return INPUT_VGA1;
    if (strcasecmp(str, "vga1") == 0) return INPUT_VGA1;
    if (strcasecmp(str, "vga2") == 0) return INPUT_VGA2;
    return -1;
}

int main(int argc, char *argv[])
{
    // 初始化
#ifdef _WIN32
    if (win_ddc_init() < 0) {
        fprintf(stderr, "初始化DDC失败\n");
        return 1;
    }
#else
    int bus = linux_find_ddc_bus();
    if (bus < 0) {
        fprintf(stderr, "找不到DDC总线\n");
        return 1;
    }
    if (ddc_init(bus) < 0) {
        fprintf(stderr, "初始化DDC失败\n");
        return 1;
    }
#endif
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"brightness", required_argument, 0, 'b'},
        {"contrast", required_argument, 0, 'c'},
        {"volume", required_argument, 0, 'v'},
        {"mute", no_argument, 0, 'm'},
        {"unmute", no_argument, 0, 'u'},
        {"input", required_argument, 0, 'i'},
        {"power-on", no_argument, 0, 'P'},
        {"power-off", no_argument, 0, 'p'},
        {"info", no_argument, 0, 'I'},
        {"help", no_argument, 0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    int option_index = 0;
    
    while ((opt = getopt_long(argc, argv, "b:c:v:mui:PpIh", long_options, &option_index)) != -1) {
        switch (opt) {
        case 'b': {
            // 设置亮度
            int brightness = atoi(optarg);
            if (brightness < 0 || brightness > 100) {
                fprintf(stderr, "亮度范围: 0-100\n");
                return 1;
            }
#ifdef _WIN32
            win_ddc_set_brightness(brightness);
#else
            ddc_set_brightness(brightness);
#endif
            printf("亮度已设置为: %d\n", brightness);
            break;
        }
        
        case 'c': {
            // 设置对比度
            int contrast = atoi(optarg);
#ifdef _WIN32
            win_ddc_set_contrast(contrast);
#else
            ddc_set_contrast(contrast);
#endif
            printf("对比度已设置为: %d\n", contrast);
            break;
        }
        
        case 'v': {
            // 设置音量
            int volume = atoi(optarg);
#ifdef _WIN32
            win_ddc_set_volume(volume);
#else
            ddc_set_volume(volume);
#endif
            printf("音量已设置为: %d\n", volume);
            break;
        }
        
        case 'm':
            // 静音
            ddc_set_mute(1);
            printf("已静音\n");
            break;
        
        case 'u':
            // 取消静音
            ddc_set_mute(0);
            printf("已取消静音\n");
            break;
        
        case 'i': {
            // 切换输入源
            int input = parse_input_source(optarg);
            if (input < 0) {
                fprintf(stderr, "未知输入源: %s\n", optarg);
                return 1;
            }
            ddc_set_input_source(input);
            printf("输入源已切换到: %s\n", optarg);
            break;
        }
        
        case 'P':
            // 开机 (通过CEC)
            cec_power_on_tv();
            printf("已发送开机命令\n");
            break;
        
        case 'p':
            // 关机 (通过CEC)
            cec_standby_tv();
            printf("已发送关机命令\n");
            break;
        
        case 'I': {
            // 显示信息
            uint8_t brightness, contrast, volume;
            uint8_t max_b, max_c, max_v;
            
            printf("=== 显示器信息 ===\n");
            
#ifdef _WIN32
            DWORD min, cur, max;
            if (win_ddc_get_brightness(&min, &cur, &max) == 0) {
                printf("亮度: %lu (最大: %lu)\n", cur, max);
            }
            if (win_ddc_get_contrast(&min, &cur, &max) == 0) {
                printf("对比度: %lu (最大: %lu)\n", cur, max);
            }
            if (win_ddc_get_volume(&cur, &max) == 0) {
                printf("音量: %lu (最大: %lu)\n", cur, max);
            }
#else
            if (ddc_get_brightness(&brightness, &max_b) == 0) {
                printf("亮度: %d (最大: %d)\n", brightness, max_b);
            }
            if (ddc_get_contrast(&contrast, &max_c) == 0) {
                printf("对比度: %d (最大: %d)\n", contrast, max_c);
            }
            if (ddc_get_volume(&volume, &max_v) == 0) {
                printf("音量: %d (最大: %d)\n", volume, max_v);
            }
#endif
            
            // 读取能力字符串
            char caps[1024];
            if (ddc_get_capabilities(caps, sizeof(caps)) > 0) {
                MonitorCapabilities_t mon_caps;
                parse_capabilities(caps, &mon_caps);
                print_monitor_capabilities(&mon_caps);
            }
            break;
        }
        
        case 'h':
        default:
            print_usage(argv[0]);
            return 0;
        }
    }
    
    // 清理
#ifdef _WIN32
    win_ddc_close();
#endif
    
    return 0;
}

Python封装

python 复制代码
#!/usr/bin/env python3
"""
DDC/CI Python封装

依赖: pip install monitorcontrol
"""

from monitorcontrol import get_monitors

def get_all_monitors():
    """获取所有显示器"""
    monitors = get_monitors()
    return monitors

def set_brightness(monitor_index, brightness):
    """设置指定显示器亮度"""
    monitors = get_monitors()
    if monitor_index >= len(monitors):
        raise ValueError(f"显示器索引 {monitor_index} 不存在")
    
    with monitors[monitor_index]:
        monitors[monitor_index].set_luminance(brightness)
    print(f"显示器 {monitor_index} 亮度已设置为 {brightness}")

def get_brightness(monitor_index):
    """获取指定显示器亮度"""
    monitors = get_monitors()
    with monitors[monitor_index]:
        return monitors[monitor_index].get_luminance()

def set_contrast(monitor_index, contrast):
    """设置对比度"""
    monitors = get_monitors()
    with monitors[monitor_index]:
        monitors[monitor_index].set_contrast(contrast)

def set_input_source(monitor_index, source):
    """切换输入源
    
    source: 'hdmi1', 'hdmi2', 'dp1', 'dp2', 'vga1'
    """
    from monitorcontrol import InputSource
    
    source_map = {
        'hdmi1': InputSource.HDMI1,
        'hdmi2': InputSource.HDMI2,
        'dp1': InputSource.DP1,
        'dp2': InputSource.DP2,
        'vga1': InputSource.ANALOG1,
    }
    
    if source not in source_map:
        raise ValueError(f"未知输入源: {source}")
    
    monitors = get_monitors()
    with monitors[monitor_index]:
        monitors[monitor_index].set_input_source(source_map[source])

def list_monitors():
    """列出所有显示器信息"""
    monitors = get_monitors()
    
    for i, monitor in enumerate(monitors):
        print(f"=== 显示器 {i} ===")
        with monitor:
            try:
                print(f"  亮度: {monitor.get_luminance()}")
            except:
                print("  亮度: 不支持")
            
            try:
                print(f"  对比度: {monitor.get_contrast()}")
            except:
                print("  对比度: 不支持")
            
            try:
                caps = monitor.get_vcp_capabilities()
                print(f"  能力: {caps}")
            except:
                pass

if __name__ == "__main__":
    import sys
    
    if len(sys.argv) < 2:
        print("用法:")
        print("  python ddc_control.py list")
        print("  python ddc_control.py brightness <显示器索引> <值>")
        print("  python ddc_control.py input <显示器索引> <源>")
        sys.exit(0)
    
    cmd = sys.argv[1]
    
    if cmd == "list":
        list_monitors()
    
    elif cmd == "brightness":
        idx = int(sys.argv[2])
        val = int(sys.argv[3])
        set_brightness(idx, val)
    
    elif cmd == "input":
        idx = int(sys.argv[2])
        src = sys.argv[3]
        set_input_source(idx, src)

常见问题与解决

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        常见问题与解决方案                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   Q1: DDC/CI不工作,读取/设置失败                                           │
│   ─────────────────────────────────────────                                 │
│   可能原因:                                                                 │
│   1. 显示器不支持DDC/CI (检查显示器规格)                                    │
│   2. 显示器DDC/CI功能被关闭 (进入OSD菜单开启)                               │
│   3. 使用了不支持DDC的转接器 (HDMI转VGA等)                                  │
│   4. 驱动问题 (更新显卡驱动)                                                │
│   5. I2C总线被占用 (Linux下检查i2c权限)                                     │
│                                                                             │
│   解决方法:                                                                 │
│   - Windows: 安装 ControlMyMonitor / Twinkle Tray                          │
│   - Linux: 安装 ddcutil,添加用户到i2c组                                    │
│     sudo usermod -aG i2c $USER                                             │
│                                                                             │
│   Q2: CEC不工作                                                             │
│   ─────────────────────────────────────────                                 │
│   可能原因:                                                                 │
│   1. 显卡不支持CEC (大多数PC显卡不支持)                                     │
│   2. 需要专门的CEC适配器 (如Pulse-Eight USB-CEC)                            │
│   3. 电视CEC功能被关闭                                                      │
│                                                                             │
│   解决方法:                                                                 │
│   - 购买USB-CEC适配器                                                       │
│   - 使用libcec库                                                           │
│                                                                             │
│   Q3: 某些VCP代码不支持                                                     │
│   ─────────────────────────────────────────                                 │
│   不同显示器支持的VCP代码不同,需要先读取能力字符串确认                      │
│                                                                             │
│   Q4: 设置后没有效果                                                        │
│   ─────────────────────────────────────────                                 │
│   - 某些显示器需要延时才能看到效果                                          │
│   - 某些值可能被显示器限制在特定范围                                        │
│   - 读取最大值确认有效范围                                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

总结

HDMI反向控制显示器的核心技术:

协议 用途 通道 典型操作
DDC/CI 显示器参数控制 I2C (Pin 15/16) 亮度、对比度、音量、输入源
CEC 设备联动控制 单线 (Pin 13) 电源、遥控器透传、一键播放
EDID 显示器信息 I2C (Pin 15/16) 分辨率、型号、能力

DDC/CI常用VCP代码:

VCP代码 功能 范围
0x10 亮度 0-100
0x12 对比度 0-100
0x60 输入源 见InputSource
0x62 音量 0-100
0x8D 静音 0x01/0x02
0xD6 电源模式 0x01-0x05

平台支持:

  • Windows: PhysicalMonitor API / ControlMyMonitor
  • Linux: ddcutil / 直接I2C访问
  • CEC需要专门硬件支持(USB-CEC适配器)

希望这篇文章对做显示器控制的朋友有帮助!有问题欢迎评论区交流~


参考资料:

  • VESA MCCS (Monitor Control Command Set) 标准
  • HDMI-CEC 规范
  • DDC/CI 协议文档
  • libcec 项目
  • ddcutil 文档
相关推荐
虫小宝1 小时前
导购APP容器化CI/CD流程:Jenkins在返利系统持续部署中的实践
运维·ci/cd·jenkins
黛玉晴雯子0012 小时前
Devops基础之Jenkins持续集成工具(持续更新)
ci/cd·jenkins·devops
TheNextByte13 小时前
如何在不使用USB数据线的情况下将文件从电脑传到安卓手机?
android·智能手机·电脑
信创天地3 小时前
信创环境下CI/CD与灾备体系构建:从异构挑战到自主可控的运维革命
运维·ci/cd
熊猫钓鱼>_>3 小时前
对话式部署实践:从零开始使用TRAE SOLO构建自动化CI/CD Pipeline
运维·ci/cd·自动化·devops·trae·solo·trae solo
tzhou644521 天前
Docker的CICD持续集成
ci/cd·docker·容器
叫致寒吧1 天前
CICD持续集成Ruo-Yi项目
ci/cd
少云清1 天前
【接口测试】2_持续集成 _Git与Gitee
git·ci/cd·gitee
开开心心就好1 天前
右键菜单管理工具,添加程序自定义名称位置
linux·运维·服务器·ci/cd·docker·pdf·1024程序员节