HDMI-CEC 控制服务

简介

高清多媒体接口消费类电子产品控制 (HDMI-CEC) 标准允许多媒体消费类商品相互通信和交换信息。HDMI-CEC 支持很多功能(如遥控直通和系统音频控制),但最受欢迎的功能之一是单键播放。单键播放功能可以让媒体来源设备打开电视并自动切换其输入端口,因此您无需寻找电视遥控器,即可从 Chromecast 切换到蓝光播放器。

大多数制造商都已采用 HDMI-CEC,因此它们的设备能够与其他公司的设备配合使用。不过,由于各个制造商实现 HDMI-CEC 标准的方式不同,因此设备之间并不总是可以实现顺畅协作,而且支持的功能也因设备而异。由于存在这种差异,消费者并不能放心地假定两个声称支持 CEC 的产品是完全兼容的。

解决方法

随着 Android 电视输入框架 (TIF) 的推出,HDMI-CEC 可以将所有连接的设备汇集在一起,并最大限度地减少兼容性问题。Android 已经创建了一项名为 HdmiControlService 的系统服务来帮助缓解此类难题。

通过提供 HdmiControlService 作为 Android 生态系统的一部分,Android 希望提供:

  • 一个面向所有制造商的 HDMI-CEC 标准实现方案,以降低设备的不兼容性。以前,制造商必须开发自己的 HDMI-CEC 实现方案或使用第三方解决方案。
  • 一项针对市场上已有的众多 HDMI-CEC 设备进行测试后反响良好的服务。Android 对从产品中发现的兼容性问题进行了认真而细致的研究,并从深谙该技术的设备实现人员处收集了实用的建议。CEC 服务旨在保持标准与对该标准做出的修改之间的良好平衡,以便它可以用于用户已经在使用的产品。

整体设计

HdmiControlService 与系统的其余部分(电视输入框架 (TIF)、音频服务和电源服务等)连接,以实现该标准指定的各种功能。

以下示意图描绘了从自定义 CEC 控制器到更简单的 HDMI-CEC 硬件抽象层 (HAL) 实现的转换。

展示 HDMI-CEC 在 Android 5.0 之前和之后的版本中的实现方式的示意图

图 1. HDMI 控制服务替换

实现

请参阅以下示意图,详细了解 HDMI 控制服务。

展示 HDMI 控制服务详情的图片

图 2. HDMI 控制服务详情

以下是正确的 Android HDMI-CEC 实现的关键因素:

  • 管理器类 HdmiControlManager 向特权应用提供 API。TV Input Manager 服务和音频服务等系统服务可以直接获取该服务。
  • 该服务旨在允许托管多种类型的逻辑设备。
  • HDMI-CEC 通过硬件抽象层 (HAL) 与硬件连接,以简化对设备之间协议和信号传输机制的差异的处理。设备制造商可以利用 HAL 定义来实现 HAL 层。

注意:设备制造商应将以下行添加到 device.mk 中的 PRODUCT_COPY_FILES 内。

PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.hdmi.cec.xml:system/etc/permissions/android.hardware.hdmi.cec.xml

设备制造商需要在 device.mk 中设置 ro.hdmi.device_type 以使 HdmiControlService 正常工作,具体取决于您的设备是 HDMI 接收设备还是 HDMI 源设备。

对于机顶盒 (OTT) 等 HDMI 源设备,请采用如下设置:

PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=4

对于平板电视等 HDMI 接收设备,请采用如下设置:

PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=0

  • 设备制造商提供的专有 CEC 控制器不能与 HdmiControlService 同时使用。必须将其停用或移除。在需要处理特定于制造商的命令时通常会用到此类控制器。应通过对该服务进行扩展/修改,将特定于制造商的命令处理程序合并到该服务中。上述工作由设备制造商负责,且并非由 Android 指定。请注意,针对特定于制造商的命令对该服务做出的任何更改均不得干扰标准命令的处理方式,否则将导致设备与 Android 不兼容。
  • 对 HDMI-CEC 服务的访问受 SignatureOrSystem 保护级别的保护。只有系统组件或 /system/priv-app 中的应用才能访问该服务。这是为了防止该服务被恶意应用滥用。

Android 支持类型 TV/Display(0)playback device(4),它们可向显示器发出单键播放命令。目前不支持其他类型(调谐器和录制器)。

HDMI-CEC HAL 定义

为使该服务发挥作用,必须按照 Android 提供的定义来实现 HDMI-CEC HAL。HDMI-CEC HAL 会抽象化硬件级别的差异,并通过 API 将原始操作(分配/读取/写入等)提供给上层。

设备制造商必须支持的 API 调用如下:

TX/RX/事件

  • send_message
  • register_event_callback

信息

  • get_physical_address
  • get_version
  • get_vendor_id
  • get_port_info

逻辑地址

  • add_logical_address
  • clear_logical_address

状态

  • is_connected set_option
  • set_audio_return_channel

以下是关于 API 的 HDMI-CEC HAL 定义的摘录:

#ifndef ANDROID_INCLUDE_HARDWARE_HDMI_CEC_H
#define ANDROID_INCLUDE_HARDWARE_HDMI_CEC_H

...

/*
 * HDMI-CEC HAL interface definition.
 */
typedef struct hdmi_cec_device {
    /**
     * Common methods of the HDMI-CEC device.  This *must* be the first member of
     * hdmi_cec_device as users of this structure will cast a hw_device_t to hdmi_cec_device
     * pointer in contexts where it's known the hw_device_t references a hdmi_cec_device.
     */
    struct hw_device_t common;

    /*
     * (*add_logical_address)() passes the logical address that will be used
     * in this system.
     *
     * HAL may use it to configure the hardware so that the CEC commands addressed
     * the given logical address can be filtered in. This method can be called
     * as many times as necessary in order to support multiple logical devices.
     * addr should be in the range of valid logical addresses for the call
     * to succeed.
     *
     * Returns 0 on success or -errno on error.
     */
    int (*add_logical_address)(const struct hdmi_cec_device* dev, cec_logical_address_t addr);

    /*
     * (*clear_logical_address)() tells HAL to reset all the logical addresses.
     *
     * It is used when the system doesn't need to process CEC command any more,
     * hence to tell HAL to stop receiving commands from the CEC bus, and change
     * the state back to the beginning.
     */
    void (*clear_logical_address)(const struct hdmi_cec_device* dev);

    /*
     * (*get_physical_address)() returns the CEC physical address. The
     * address is written to addr.
     *
     * The physical address depends on the topology of the network formed
     * by connected HDMI devices. It is therefore likely to change if the cable
     * is plugged off and on again. It is advised to call get_physical_address
     * to get the updated address when hot plug event takes place.
     *
     * Returns 0 on success or -errno on error.
     */
    int (*get_physical_address)(const struct hdmi_cec_device* dev, uint16_t* addr);

    /*
     * (*send_message)() transmits HDMI-CEC message to other HDMI device.
     *
     * The method should be designed to return in a certain amount of time not
     * hanging forever, which can happen if CEC signal line is pulled low for
     * some reason. HAL implementation should take the situation into account
     * so as not to wait forever for the message to get sent out.
     *
     * It should try retransmission at least once as specified in the standard.
     *
     * Returns error code. See HDMI_RESULT_SUCCESS, HDMI_RESULT_NACK, and
     * HDMI_RESULT_BUSY.
     */
    int (*send_message)(const struct hdmi_cec_device* dev, const cec_message_t*);

    /*
     * (*register_event_callback)() registers a callback that HDMI-CEC HAL
     * can later use for incoming CEC messages or internal HDMI events.
     * When calling from C++, use the argument arg to pass the calling object.
     * It will be passed back when the callback is invoked so that the context
     * can be retrieved.
     */
    void (*register_event_callback)(const struct hdmi_cec_device* dev,
            event_callback_t callback, void* arg);

    /*
     * (*get_version)() returns the CEC version supported by underlying hardware.
     */
    void (*get_version)(const struct hdmi_cec_device* dev, int* version);

    /*
     * (*get_vendor_id)() returns the identifier of the vendor. It is
     * the 24-bit unique company ID obtained from the IEEE Registration
     * Authority Committee (RAC).
     */
    void (*get_vendor_id)(const struct hdmi_cec_device* dev, uint32_t* vendor_id);

    /*
     * (*get_port_info)() returns the hdmi port information of underlying hardware.
     * info is the list of HDMI port information, and 'total' is the number of
     * HDMI ports in the system.
     */
    void (*get_port_info)(const struct hdmi_cec_device* dev,
            struct hdmi_port_info* list[], int* total);

    /*
     * (*set_option)() passes flags controlling the way HDMI-CEC service works down
     * to HAL implementation. Those flags will be used in case the feature needs
     * update in HAL itself, firmware or microcontroller.
     */
    void (*set_option)(const struct hdmi_cec_device* dev, int flag, int value);

    /*
     * (*set_audio_return_channel)() configures ARC circuit in the hardware logic
     * to start or stop the feature. Flag can be either 1 to start the feature
     * or 0 to stop it.
     *
     * Returns 0 on success or -errno on error.
     */
    void (*set_audio_return_channel)(const struct hdmi_cec_device* dev, int flag);

    /*
     * (*is_connected)() returns the connection status of the specified port.
     * Returns HDMI_CONNECTED if a device is connected, otherwise HDMI_NOT_CONNECTED.
     * The HAL should watch for +5V power signal to determine the status.
     */
    int (*is_connected)(const struct hdmi_cec_device* dev, int port);

    /* Reserved for future use to maximum 16 functions. Must be NULL. */
    void* reserved[16 - 11];
} hdmi_cec_device_t;

#endif /* ANDROID_INCLUDE_HARDWARE_HDMI_CEC_H */

借助 API,该服务可以利用硬件资源来发送/接收 HDMI-CEC 命令、配置必要的设置,并(可选)与底层平台(将在 Android 系统处于待机模式时负责 CEC 控制)上的微处理器进行通信。

测试

设备制造商必须使用自己的工具测试 HDMI-CEC HAL 的 API,以确保它们可以提供预期的功能。