Moment For Technology

Android HDMI-CEC source code analysis

Posted on Sept. 23, 2022, 10:19 p.m. by Robert Maddox
Category: android Tag: android

Based on Android11, the name of the company mentioned in the article is replaced by company

What is the HDMI CEC

Hdmi-cec is short for HIGH Definition Multimedia Interface consumer Electronics Control. It allows multimedia consumer goods to communicate and exchange information with each other. For example, two devices in your home, a TV and a TV box, are connected via HDMI cable, allowing the contents of the TV box to be displayed on the TV. If both Settings support HDMI-CEC, the two devices can control each other. For example, if you use the TV box remote control to open the box, the TV will turn on at the same time, and when you turn off the TV, the TV box will turn off. There is also the ability to use the TV's remote control to control the TV box.

What features does HDMI-CEC provide

  • System StandBy
  • OneTouchPlay
  • Remote Control Passthrough
  • System Audio Control

The overall design

Hdmi Cec configuration. -

The HDMI-CEC code of Android can support both Source and Sink terminals. For example, the TV is connected to the box through Hdmi, and the box is the Source and TV as the Sink terminals. This article mainly tells about the Tv terminal.

In order for HdmiControlService to work properly, you need to:

Copy the code

Add the following configuration

Copy the code

For HDMI source devices such as SET-top boxes (OTT), add the following:

PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=4
Copy the code

For HDMI receiving devices such as flat screen TVS, add the following:

PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=0
Copy the code

Android supports HDMI-CEC by designing a system Service: HdmiControlService, which is responsible for the hdMI-CEC connection to the application layer and Hal layer, that is, the App through the HdmiControlService interface to interact with the HDMI-Cechal layer. Let's start with the HdmiControlService.

HdmiControlService start

# frameworks/base/services/java/com/android/server/  
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {...if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
            / / only the configuration/frameworks/native/data/etc/android. Hardware. Hdmi. Cec, will go to the logic here
                t.traceBegin("StartHdmiControlService"); mSystemServiceManager.startService(HdmiControlService.class); t.traceEnd(); }... }Copy the code

HdmiControlService is responsible for almost all functions of HDMI-CEC in the Java Framework. Of course, HdmiControlService also involves MHL related functions, which will not be covered in this article.


HdmiControlService is started as a system service, so let's look at its initialization method onStart.

    public void onStart(a) {
        if (mIoLooper == null) {
            mIoLooper = mIoThread.getLooper();
        // Initialize the power status. The default value is POWER_STATUS_TRANSIENT_TO_STANDBY
        mPowerStatus = getInitialPowerStatus();
        mProhibitMode = false;
        // Check whether the CEC switch of the scheduling device is turned on
        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
        // Check whether the volume control CEC switch is enabled
        mHdmiCecVolumeControlEnabled = readBooleanSetting(
                Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true);
        mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
                // instantiate HdmiCecController ①
        if (mCecController == null) {
            mCecController = HdmiCecController.create(this);
        if(mCecController ! =null) {
            if (mHdmiControlEnabled) {
            } else {
                mCecController.setOption(OptionKey.ENABLE_CEC, false); }}else {
            // If mCecController is empty, CEC is not supported.
            Slog.i(TAG, "Device does not support HDMI-CEC.");
        if (mMhlController == null) {
            mMhlController = HdmiMhlControllerStub.create(this);
        if(! mMhlController.isReady()) { Slog.i(TAG,"Device does not support MHL-control.");
        mMhlDevices = Collections.emptyList();
                // Initializes the port information to merge ceC and MHL ports
        if (mMessageValidator == null) {
            mMessageValidator = new HdmiCecMessageValidator(this);
        // Add HdmiControlService to the ServiceManager
        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
        if(mCecController ! =null) {
            // Register broadcast receiver for power state change.
            IntentFilter filter = new IntentFilter();
            getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
            // Register ContentObserver to monitor the settings change.
        mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
Copy the code

Instantiate HdmiCecController, which manages CEC commands and behavior, and calls CEC HAL to interact with other devices. We'll talk more about that in a minute.


The HdmiCecController is instantiated using the following method

Copy the code
static HdmiCecController create(HdmiControlService service) {
        return createWithNativeWrapper(service, new NativeWrapperImpl());
Copy the code
/** * A factory method with injection of native methods for testing. */
    static HdmiCecController createWithNativeWrapper( HdmiControlService service, NativeWrapper nativeWrapper) {
        HdmiCecController controller = new HdmiCecController(service, nativeWrapper);
        // Connect to the server side of the HDMI-CEC HIDL interface
        String nativePtr = nativeWrapper.nativeInit();
        if (nativePtr == null) {
            HdmiLogger.warning("Couldn't get tv.cec service.");
            return null;
        return controller;
Copy the code


All HIDL interfaces of HDMI-CEC are called in NativeWrapperImpl

String nativePtr = nativeWrapper.nativeInit();
Copy the code
        public String nativeInit(a) {
          // Connect to the hidL Server
            return (connectToHal() ? mHdmiCec.toString() : null);
Copy the code
 boolean connectToHal(a) {
            try {
                // Get the hdMI-CEC implementation
                mHdmiCec = IHdmiCec.getService();
                try {
                    mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
                } catch (RemoteException e) {
                    HdmiLogger.error("Couldn't link to death : ", e); }}catch (RemoteException e) {
                HdmiLogger.error("Couldn't get tv.cec service : ", e);
                return false;
            return true;
Copy the code

IHdmiCec is a HIDL interface through which all interactions with the CEC Hal layer are implemented. The upper layer obtains the implementation of IHdmiCec through ihdmicec.getService (), so that the upper layer can invoke the implementation of HDMI-CEC through the HIDL interface. This is also a fixed notation for HIDL Client.

Hal interface definition

Let's look at the interfaces defined by the HdmiCec Hal layer

/ / hardware/interfaces/TV/cec / 1.0 / IHdmiCec. Hal


import IHdmiCecCallback;

/** * HDMI-CEC HAL interface definition. */
interface IHdmiCec {
    /** * Passes the logical address that must be used in this system. * * HAL must use it to configure the hardware so that  the CEC commands * addressed the given logical address can be filtered in. This method must * be able to be called as many times as necessary in order to support * multiple logical devices. * *@paramaddr Logical address that must be used in this system. It must be * in the range of valid logical addresses for the call  to succeed. *@return result Result status of the operation. SUCCESS if successful,
     *         FAILURE_INVALID_ARGS if the given logical address is invalid,
     *         FAILURE_BUSY if device or resource is busy
    addLogicalAddress(CecLogicalAddress addr) generates (Result result);

    /** * Clears 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. */

     * Gets the CEC physical address.
     * 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 getPhysicalAddress to get
     * the updated address when hot plug event takes place.
     * @return result Result status of the operation. SUCCESS if successful,
     *         FAILURE_INVALID_STATE if HAL cannot retrieve the physical
     *         address.
     * @return addr Physical address of this device.
    getPhysicalAddress() generates (Result result, uint16_t addr);

    /** * Transmits HDMI-CEC message to other HDMI device. * * The method must be designed to return in a certain amount of time and not * hanging forever which may happen if CEC signal line is pulled low for * some reason. * * It must try Retransmission at least once as specified in the section '7.1 * Frame Re-televised 'of the CEC Spec@param message CEC message to be sent to other HDMI device.
     * @return result Result status of the operation. SUCCESS if successful,
     *         NACK if the sent message is not acknowledged,
     *         BUSY if the CEC bus is busy.
    sendMessage(CecMessage message) generates (SendMessageResult result);

     * Sets a callback that HDMI-CEC HAL must later use for incoming CEC
     * messages or internal HDMI events.
     * @param callback Callback object to pass hdmi events to the system. The
     *        previously registered callback must be replaced with this one.
    setCallback(IHdmiCecCallback callback);

     * Returns the CEC version supported by underlying hardware.
     * @return version the CEC version supported by underlying hardware.
    getCecVersion() generates (int32_t version);

     * Gets the identifier of the vendor.
     * @returnvendorId Identifier of the vendor that is the 24-bit unique * company ID obtained from the IEEE Registration Authority *  Committee (RAC). The upper 8 bits must be 0. */
    getVendorId() generates (uint32_t vendorId);

     * Gets the hdmi port information of underlying hardware.
     * @return infos The list of HDMI port information
    getPortInfo() generates (vecHdmiPortInfo infos);

     * Sets flags controlling the way HDMI-CEC service works down to HAL
     * implementation. Those flags must be used in case the feature needs update
     * in HAL itself, firmware or microcontroller.
     * @param key The key of the option to be updated with a new value.
     * @param value Value to be set.
    setOption(OptionKey key, bool value);

     * Passes the updated language information of Android system. Contains
     * three-letter code as defined in ISO/FDIS 639-2. Must be used for HAL to
     * respond to Get Menu Language while in standby mode.
     * @param language Three-letter code defined in ISO/FDIS 639-2. Must be
     *        lowercase letters. (e.g., eng for English)
    setLanguage(string language);

     * Configures ARC circuit in the hardware logic to start or stop the
     * feature.
     * @param portId Port id to be configured.
     * @param enable Flag must be either true to start the feature or false to
     *        stop it.
    enableAudioReturnChannel(int32_t portId, bool enable);

     * Gets the connection status of the specified port.
     * @param portId Port id to be inspected for the connection status.
     * @return status True if a device is connected, otherwise false. The HAL
     *         must watch for +5V power signal to determine the status.
    isConnected(int32_t portId) generates (bool status);

Copy the code

The above interfaces are all interfaces provided by hdmi-cec to the upper Frameworkd, and all hdmi-cec functions are directly or indirectly implemented through these interfaces.

HdmiCec interface implementation

#define LOG_TAG ""
#include android-base/logging.h

#include hardware/hardware.h
#include hardware/hdmi_cec.h
#include "HdmiCec.h"

namespace android {
namespace hardware {
namespace tv {
namespace cec {
namespace V1_0 {
namespace implementation {

static_assert(CEC_DEVICE_INACTIVE == static_castint(CecDeviceType::INACTIVE),
        "CecDeviceType::INACTIVE must match legacy value.");
static_assert(CEC_DEVICE_TV == static_castint(CecDeviceType::TV),
        "CecDeviceType::TV must match legacy value.");
static_assert(CEC_DEVICE_RECORDER == static_castint(CecDeviceType::RECORDER),
        "CecDeviceType::RECORDER must match legacy value.");
static_assert(CEC_DEVICE_TUNER == static_castint(CecDeviceType::TUNER),
        "CecDeviceType::TUNER must match legacy value.");
static_assert(CEC_DEVICE_PLAYBACK == static_castint(CecDeviceType::PLAYBACK),
        "CecDeviceType::PLAYBACK must match legacy value.");
static_assert(CEC_DEVICE_AUDIO_SYSTEM == static_castint(CecDeviceType::AUDIO_SYSTEM),
        "CecDeviceType::AUDIO_SYSTEM must match legacy value.");
static_assert(CEC_DEVICE_MAX == static_castint(CecDeviceType::MAX),
        "CecDeviceType::MAX must match legacy value.");

static_assert(CEC_ADDR_TV == static_castint(CecLogicalAddress::TV),
        "CecLogicalAddress::TV must match legacy value.");
static_assert(CEC_ADDR_RECORDER_1 == static_castint(CecLogicalAddress::RECORDER_1),
        "CecLogicalAddress::RECORDER_1 must match legacy value.");
static_assert(CEC_ADDR_RECORDER_2 == static_castint(CecLogicalAddress::RECORDER_2),
        "CecLogicalAddress::RECORDER_2 must match legacy value.");
static_assert(CEC_ADDR_TUNER_1 == static_castint(CecLogicalAddress::TUNER_1),
        "CecLogicalAddress::TUNER_1 must match legacy value.");
static_assert(CEC_ADDR_PLAYBACK_1 == static_castint(CecLogicalAddress::PLAYBACK_1),
        "CecLogicalAddress::PLAYBACK_1 must match legacy value.");
static_assert(CEC_ADDR_AUDIO_SYSTEM == static_castint(CecLogicalAddress::AUDIO_SYSTEM),
        "CecLogicalAddress::AUDIO_SYSTEM must match legacy value.");
static_assert(CEC_ADDR_TUNER_2 == static_castint(CecLogicalAddress::TUNER_2),
        "CecLogicalAddress::TUNER_2 must match legacy value.");
static_assert(CEC_ADDR_TUNER_3 == static_castint(CecLogicalAddress::TUNER_3),
        "CecLogicalAddress::TUNER_3 must match legacy value.");
static_assert(CEC_ADDR_PLAYBACK_2 == static_castint(CecLogicalAddress::PLAYBACK_2),
        "CecLogicalAddress::PLAYBACK_2 must match legacy value.");
static_assert(CEC_ADDR_RECORDER_3 == static_castint(CecLogicalAddress::RECORDER_3),
        "CecLogicalAddress::RECORDER_3 must match legacy value.");
static_assert(CEC_ADDR_TUNER_4 == static_castint(CecLogicalAddress::TUNER_4),
        "CecLogicalAddress::TUNER_4 must match legacy value.");
static_assert(CEC_ADDR_PLAYBACK_3 == static_castint(CecLogicalAddress::PLAYBACK_3),
        "CecLogicalAddress::PLAYBACK_3 must match legacy value.");
static_assert(CEC_ADDR_FREE_USE == static_castint(CecLogicalAddress::FREE_USE),
        "CecLogicalAddress::FREE_USE must match legacy value.");
static_assert(CEC_ADDR_UNREGISTERED == static_castint(CecLogicalAddress::UNREGISTERED),
        "CecLogicalAddress::UNREGISTERED must match legacy value.");
static_assert(CEC_ADDR_BROADCAST == static_castint(CecLogicalAddress::BROADCAST),
        "CecLogicalAddress::BROADCAST must match legacy value.");

static_assert(CEC_MESSAGE_FEATURE_ABORT == static_castint(CecMessageType::FEATURE_ABORT),
        "CecMessageType::FEATURE_ABORT must match legacy value.");
static_assert(CEC_MESSAGE_IMAGE_VIEW_ON == static_castint(CecMessageType::IMAGE_VIEW_ON),
        "CecMessageType::IMAGE_VIEW_ON must match legacy value.");
static_assert(CEC_MESSAGE_TUNER_STEP_INCREMENT == static_castint(
        "CecMessageType::TUNER_STEP_INCREMENT must match legacy value.");
static_assert(CEC_MESSAGE_TUNER_STEP_DECREMENT == static_castint(
        "CecMessageType::TUNER_STEP_DECREMENT must match legacy value.");
static_assert(CEC_MESSAGE_TUNER_DEVICE_STATUS == static_castint(
        "CecMessageType::TUNER_DEVICE_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_GIVE_TUNER_DEVICE_STATUS == static_castint(
        "CecMessageType::GIVE_TUNER_DEVICE_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_RECORD_ON == static_castint(CecMessageType::RECORD_ON),
        "CecMessageType::RECORD_ON must match legacy value.");
static_assert(CEC_MESSAGE_RECORD_STATUS == static_castint(CecMessageType::RECORD_STATUS),
        "CecMessageType::RECORD_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_RECORD_OFF == static_castint(CecMessageType::RECORD_OFF),
        "CecMessageType::RECORD_OFF must match legacy value.");
static_assert(CEC_MESSAGE_TEXT_VIEW_ON == static_castint(CecMessageType::TEXT_VIEW_ON),
        "CecMessageType::TEXT_VIEW_ON must match legacy value.");
static_assert(CEC_MESSAGE_RECORD_TV_SCREEN == static_castint(CecMessageType::RECORD_TV_SCREEN),
        "CecMessageType::RECORD_TV_SCREEN must match legacy value.");
static_assert(CEC_MESSAGE_GIVE_DECK_STATUS == static_castint(CecMessageType::GIVE_DECK_STATUS),
        "CecMessageType::GIVE_DECK_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_STANDBY == static_castint(CecMessageType::STANDBY),
        "CecMessageType::STANDBY must match legacy value.");
static_assert(CEC_MESSAGE_PLAY == static_castint(CecMessageType::PLAY),
        "CecMessageType::PLAY must match legacy value.");
static_assert(CEC_MESSAGE_DECK_CONTROL == static_castint(CecMessageType::DECK_CONTROL),
        "CecMessageType::DECK_CONTROL must match legacy value.");
static_assert(CEC_MESSAGE_TIMER_CLEARED_STATUS == static_castint(
        "CecMessageType::TIMER_CLEARED_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_USER_CONTROL_PRESSED == static_castint(
        "CecMessageType::USER_CONTROL_PRESSED must match legacy value.");
static_assert(CEC_MESSAGE_USER_CONTROL_RELEASED == static_castint(
        "CecMessageType::USER_CONTROL_RELEASED must match legacy value.");
static_assert(CEC_MESSAGE_GIVE_OSD_NAME == static_castint(CecMessageType::GIVE_OSD_NAME),
        "CecMessageType::GIVE_OSD_NAME must match legacy value.");
static_assert(CEC_MESSAGE_SET_OSD_NAME == static_castint(CecMessageType::SET_OSD_NAME),
        "CecMessageType::SET_OSD_NAME must match legacy value.");
static_assert(CEC_MESSAGE_SYSTEM_AUDIO_MODE_REQUEST == static_castint(
        "CecMessageType::SYSTEM_AUDIO_MODE_REQUEST must match legacy value.");
static_assert(CEC_MESSAGE_GIVE_AUDIO_STATUS == static_castint(CecMessageType::GIVE_AUDIO_STATUS),
        "CecMessageType::GIVE_AUDIO_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_SET_SYSTEM_AUDIO_MODE == static_castint(
        "CecMessageType::SET_SYSTEM_AUDIO_MODE must match legacy value.");
static_assert(CEC_MESSAGE_REPORT_AUDIO_STATUS == static_castint(
        "CecMessageType::REPORT_AUDIO_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS == static_castint(
        "CecMessageType::GIVE_SYSTEM_AUDIO_MODE_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_SYSTEM_AUDIO_MODE_STATUS == static_castint(
        "CecMessageType::SYSTEM_AUDIO_MODE_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_ROUTING_CHANGE == static_castint(CecMessageType::ROUTING_CHANGE),
        "CecMessageType::ROUTING_CHANGE must match legacy value.");
static_assert(CEC_MESSAGE_ROUTING_INFORMATION == static_castint(
        "CecMessageType::ROUTING_INFORMATION must match legacy value.");
static_assert(CEC_MESSAGE_ACTIVE_SOURCE == static_castint(CecMessageType::ACTIVE_SOURCE),
        "CecMessageType::ACTIVE_SOURCE must match legacy value.");
static_assert(CEC_MESSAGE_GIVE_PHYSICAL_ADDRESS == static_castint(
        "CecMessageType::GIVE_PHYSICAL_ADDRESS must match legacy value.");
static_assert(CEC_MESSAGE_REPORT_PHYSICAL_ADDRESS == static_castint(
        "CecMessageType::REPORT_PHYSICAL_ADDRESS must match legacy value.");
static_assert(CEC_MESSAGE_REQUEST_ACTIVE_SOURCE == static_castint(
        "CecMessageType::REQUEST_ACTIVE_SOURCE must match legacy value.");
static_assert(CEC_MESSAGE_SET_STREAM_PATH == static_castint(CecMessageType::SET_STREAM_PATH),
        "CecMessageType::SET_STREAM_PATH must match legacy value.");
static_assert(CEC_MESSAGE_DEVICE_VENDOR_ID == static_castint(CecMessageType::DEVICE_VENDOR_ID),
        "CecMessageType::DEVICE_VENDOR_ID must match legacy value.");
static_assert(CEC_MESSAGE_VENDOR_COMMAND == static_castint(CecMessageType::VENDOR_COMMAND),
        "CecMessageType::VENDOR_COMMAND must match legacy value.");
static_assert(CEC_MESSAGE_VENDOR_REMOTE_BUTTON_DOWN == static_castint(
        "CecMessageType::VENDOR_REMOTE_BUTTON_DOWN must match legacy value.");
static_assert(CEC_MESSAGE_VENDOR_REMOTE_BUTTON_UP == static_castint(
        "CecMessageType::VENDOR_REMOTE_BUTTON_UP must match legacy value.");
static_assert(CEC_MESSAGE_GIVE_DEVICE_VENDOR_ID == static_castint(
        "CecMessageType::GIVE_DEVICE_VENDOR_ID must match legacy value.");
static_assert(CEC_MESSAGE_MENU_REQUEST == static_castint(CecMessageType::MENU_REQUEST),
        "CecMessageType::MENU_REQUEST must match legacy value.");
static_assert(CEC_MESSAGE_MENU_STATUS == static_castint(CecMessageType::MENU_STATUS),
        "CecMessageType::MENU_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_GIVE_DEVICE_POWER_STATUS == static_castint(
        "CecMessageType::GIVE_DEVICE_POWER_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_REPORT_POWER_STATUS == static_castint(
        "CecMessageType::REPORT_POWER_STATUS must match legacy value.");
static_assert(CEC_MESSAGE_GET_MENU_LANGUAGE == static_castint(CecMessageType::GET_MENU_LANGUAGE),
        "CecMessageType::GET_MENU_LANGUAGE must match legacy value.");
static_assert(CEC_MESSAGE_SELECT_ANALOG_SERVICE == static_castint(
        "CecMessageType::SELECT_ANALOG_SERVICE must match legacy value.");
static_assert(CEC_MESSAGE_SELECT_DIGITAL_SERVICE == static_castint(
        "CecMessageType::SELECT_DIGITAL_SERVICE must match legacy value.");
static_assert(CEC_MESSAGE_SET_DIGITAL_TIMER == static_castint(CecMessageType::SET_DIGITAL_TIMER),
        "CecMessageType::SET_DIGITAL_TIMER must match legacy value.");
static_assert(CEC_MESSAGE_CLEAR_DIGITAL_TIMER == static_castint(
        "CecMessageType::CLEAR_DIGITAL_TIMER must match legacy value.");
static_assert(CEC_MESSAGE_SET_AUDIO_RATE == static_castint(CecMessageType::SET_AUDIO_RATE),
        "CecMessageType::SET_AUDIO_RATE must match legacy value.");
static_assert(CEC_MESSAGE_INACTIVE_SOURCE == static_castint(CecMessageType::INACTIVE_SOURCE),
        "CecMessageType::INACTIVE_SOURCE must match legacy value.");
static_assert(CEC_MESSAGE_CEC_VERSION == static_castint(CecMessageType::CEC_VERSION),
        "CecMessageType::CEC_VERSION must match legacy value.");
static_assert(CEC_MESSAGE_GET_CEC_VERSION == static_castint(CecMessageType::GET_CEC_VERSION),
        "CecMessageType::GET_CEC_VERSION must match legacy value.");
static_assert(CEC_MESSAGE_VENDOR_COMMAND_WITH_ID == static_castint(
        "CecMessageType::VENDOR_COMMAND_WITH_ID must match legacy value.");
static_assert(CEC_MESSAGE_CLEAR_EXTERNAL_TIMER == static_castint(
        "CecMessageType::CLEAR_EXTERNAL_TIMER must match legacy value.");
static_assert(CEC_MESSAGE_SET_EXTERNAL_TIMER == static_castint(
        "CecMessageType::SET_EXTERNAL_TIMER must match legacy value.");
static_assert(CEC_MESSAGE_INITIATE_ARC == static_castint(CecMessageType::INITIATE_ARC),
        "CecMessageType::INITIATE_ARC must match legacy value.");
static_assert(CEC_MESSAGE_REPORT_ARC_INITIATED == static_castint(
        "CecMessageType::REPORT_ARC_INITIATED must match legacy value.");
static_assert(CEC_MESSAGE_REPORT_ARC_TERMINATED == static_castint(
        "CecMessageType::REPORT_ARC_TERMINATED must match legacy value.");
static_assert(CEC_MESSAGE_REQUEST_ARC_INITIATION == static_castint(
        "CecMessageType::REQUEST_ARC_INITIATION must match legacy value.");
static_assert(CEC_MESSAGE_REQUEST_ARC_TERMINATION == static_castint(
        "CecMessageType::REQUEST_ARC_TERMINATION must match legacy value.");
static_assert(CEC_MESSAGE_TERMINATE_ARC == static_castint(CecMessageType::TERMINATE_ARC),
        "CecMessageType::TERMINATE_ARC must match legacy value.");
static_assert(CEC_MESSAGE_ABORT == static_castint(CecMessageType::ABORT),
        "CecMessageType::ABORT must match legacy value.");

static_assert(ABORT_UNRECOGNIZED_MODE == static_castint(AbortReason::UNRECOGNIZED_MODE),
        "AbortReason::UNRECOGNIZED_MODE must match legacy value.");
static_assert(ABORT_NOT_IN_CORRECT_MODE == static_castint(AbortReason::NOT_IN_CORRECT_MODE),
        "AbortReason::NOT_IN_CORRECT_MODE must match legacy value.");
static_assert(ABORT_CANNOT_PROVIDE_SOURCE == static_castint(AbortReason::CANNOT_PROVIDE_SOURCE),
        "AbortReason::CANNOT_PROVIDE_SOURCE must match legacy value.");
static_assert(ABORT_INVALID_OPERAND == static_castint(AbortReason::INVALID_OPERAND),
        "AbortReason::INVALID_OPERAND must match legacy value.");
static_assert(ABORT_REFUSED == static_castint(AbortReason::REFUSED),
        "AbortReason::REFUSED must match legacy value.");
static_assert(ABORT_UNABLE_TO_DETERMINE == static_castint(AbortReason::UNABLE_TO_DETERMINE),
        "AbortReason::UNABLE_TO_DETERMINE must match legacy value.");

static_assert(HDMI_RESULT_SUCCESS == static_castint(SendMessageResult::SUCCESS),
        "SendMessageResult::SUCCESS must match legacy value.");
static_assert(HDMI_RESULT_NACK == static_castint(SendMessageResult::NACK),
        "SendMessageResult::NACK must match legacy value.");
static_assert(HDMI_RESULT_BUSY == static_castint(SendMessageResult::BUSY),
        "SendMessageResult::BUSY must match legacy value.");
static_assert(HDMI_RESULT_FAIL == static_castint(SendMessageResult::FAIL),
        "SendMessageResult::FAIL must match legacy value.");

static_assert(HDMI_INPUT == static_castint(HdmiPortType::INPUT),
        "HdmiPortType::INPUT must match legacy value.");
static_assert(HDMI_OUTPUT == static_castint(HdmiPortType::OUTPUT),
        "HdmiPortType::OUTPUT must match legacy value.");

static_assert(HDMI_OPTION_WAKEUP == static_castint(OptionKey::WAKEUP),
        "OptionKey::WAKEUP must match legacy value.");
static_assert(HDMI_OPTION_ENABLE_CEC == static_castint(OptionKey::ENABLE_CEC),
        "OptionKey::ENABLE_CEC must match legacy value.");
static_assert(HDMI_OPTION_SYSTEM_CEC_CONTROL == static_castint(OptionKey::SYSTEM_CEC_CONTROL),
        "OptionKey::SYSTEM_CEC_CONTROL must match legacy value.");

spIHdmiCecCallback HdmiCec::mCallback = nullptr;

HdmiCec::HdmiCec(hdmi_cec_device_t* device) : mDevice(device) {}

// Methods from ::android::hardware::tv::cec::V1_0::IHdmiCec follow.
ReturnResult HdmiCec::addLogicalAddress(CecLogicalAddress addr) {
    int ret = mDevice-add_logical_address(mDevice, static_castcec_logical_address_t(addr));
    switch (ret) {
        case 0:
            return Result::SUCCESS;
        case -EINVAL:
            return Result::FAILURE_INVALID_ARGS;
        case -ENOTSUP:
            return Result::FAILURE_NOT_SUPPORTED;
        case -EBUSY:
            return Result::FAILURE_BUSY;
            returnResult::FAILURE_UNKNOWN; }}Returnvoid HdmiCec::clearLogicalAddress(a) {
    return Void(a); }Returnvoid HdmiCec::getPhysicalAddress(getPhysicalAddress_cb _hidl_cb) {
    uint16_t addr;
    int ret = mDevice-get_physical_address(mDevice, addr);
    switch (ret) {
        case 0:
            _hidl_cb(Result::SUCCESS, addr);
        case -EBADF:
            _hidl_cb(Result::FAILURE_INVALID_STATE, addr);
            _hidl_cb(Result::FAILURE_UNKNOWN, addr);
    return Void(a); }ReturnSendMessageResult HdmiCec::sendMessage(const CecMessage message) {
    cec_message_t legacyMessage {
        .initiator = static_castcec_logical_address_t(message.initiator),
        .destination = static_castcec_logical_address_t(message.destination),
        .length = message.body.size()};for (size_t i = 0; i  message.body.size(a); ++i) { legacyMessage.body[i] =static_castunsigned char(message.body[i]);
    return static_castSendMessageResult(mDevice-send_message(mDevice, legacyMessage));

Returnvoid HdmiCec::setCallback(const spIHdmiCecCallback callback) {
    if(mCallback ! =nullptr) {
        mCallback = nullptr;

    if(callback ! =nullptr) {
        mCallback = callback;
        mCallback-linkToDeath(this.0 /*cookie*/);
        mDevice-register_event_callback(mDevice, eventCallback, nullptr);
    return Void(a); }Returnint32_t HdmiCec::getCecVersion(a) {
    int version;
    mDevice-get_version(mDevice, version);
    return static_castint32_t(version);

Returnuint32_t HdmiCec::getVendorId(a) {
    uint32_t vendor_id;
    mDevice-get_vendor_id(mDevice, vendor_id);
    return vendor_id;

Returnvoid HdmiCec::getPortInfo(getPortInfo_cb _hidl_cb) {
    struct hdmi_port_info* legacyPorts;
    int numPorts;
    hidl_vecHdmiPortInfo portInfos;
    mDevice-get_port_info(mDevice, legacyPorts, numPorts);
    for (int i = 0; i  numPorts; ++i) {
        portInfos[i] = {
            .type = static_castHdmiPortType(legacyPorts[i].type),
            .portId = static_castuint32_t(legacyPorts[i].port_id), .cecSupported = legacyPorts[i].cec_supported ! =0, .arcSupported = legacyPorts[i].arc_supported ! =0,
            .physicalAddress = legacyPorts[i].physical_address
    return Void(a); }Returnvoid HdmiCec::setOption(OptionKey key, bool value) {
    mDevice-set_option(mDevice, static_castint(key), value ? 1 : 0);
    return Void(a); }Returnvoid HdmiCec::setLanguage(const hidl_string language) {
    if (language.size() != 3) {
        LOG(ERROR)  "Wrong language code: expected 3 letters, but it was "  language.size()  ".";
        return Void(a); }const char *languageStr = language.c_str(a);int convertedLanguage = ((languageStr[0]  0xFF)  16)
            | ((languageStr[1]  0xFF)  8)
            | (languageStr[2]  0xFF);
    mDevice-set_option(mDevice, HDMI_OPTION_SET_LANG, convertedLanguage);
    return Void(a); }Returnvoid HdmiCec::enableAudioReturnChannel(int32_t portId, bool enable) {
    mDevice-set_audio_return_channel(mDevice, portId, enable ? 1 : 0);
    return Void(a); }Returnbool HdmiCec::isConnected(int32_t portId) {
    return mDevice-is_connected(mDevice, portId)  0;

IHdmiCec* HIDL_FETCH_IHdmiCec(const char* hal) {
    hdmi_cec_device_t* hdmi_cec_device;
    int ret = 0;
    const hw_module_t* hw_module = nullptr;

    ret = hw_get_module (HDMI_CEC_HARDWARE_MODULE_ID, hw_module);
    if (ret == 0) {
        ret = hdmi_cec_open (hw_module, hdmi_cec_device);
        if(ret ! =0) {
            LOG(ERROR)  "hdmi_cec_open "  hal  " failed: " ret; }}else {
        LOG(ERROR)  "hw_get_module "  hal  " failed: "  ret;

    if (ret == 0) {
        return new HdmiCec(hdmi_cec_device);
    } else {
        LOG(ERROR)  "Passthrough failed to load legacy HAL.";
        return nullptr; }}}// namespace implementation
}  // namespace V1_0
}  // namespace cec
}  // namespace tv
}  // namespace hardware
}  // namespace android

Copy the code

HIDL is divided into two types: Binderized and Passthrough. Binderized is in a different process (Impl and service) and Passthrough is in the same process. If Passthrough is used, the HIDL_FETCH_* interface needs to be exposed in the Impl, such as the HIDL_FETCH_IHdmiCec interface in the code above. In the HIDL_FETCH_IHdmiCec method, hw_get_module is used to obtain the hardware module, and hw_get_module is used to obtain the hardware module based on the module ID, such as HDMI_CEC_HARDWARE_MODULE_ID.

// hardware/libhardware/hardware.c
int hw_get_module(const char *id, const struct hw_module_t支那module)
    return hw_get_module_by_class(id, NULL.module);
Copy the code
// hardware/libhardware/hardware.c
int hw_get_module_by_class(const char *class_id, const char *inst,
                           const struct hw_module_t支那module)
    int i = 0;
    char prop[PATH_MAX] = {0};
    char path[PATH_MAX] = {0};
    char name[PATH_MAX] = {0};
    char prop_name[PATH_MAX] = {0};

    if (inst)
        snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
        strlcpy(name, class_id, PATH_MAX);

    /* * Here we rely on the fact that calling dlopen multiple times on * the same .so will simply increment a refcount (and  not load * a new copy of the library). * We also assume that dlopen() is thread-safe. */

    /* First try a property specific to the class and possibly instance */
    snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
    if (property_get(prop_name, prop, NULL)  0) {
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            gotofound; }}/* Loop through the configuration variants looking for a module */
    for (i=0 ; iHAL_VARIANT_KEYS_COUNT; i++) {
        if (property_get(variant_keys[i], prop, NULL) = =0) {
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            gotofound; }}/* Nothing found, try the default */
    if (hw_module_exists(path, sizeof(path), name, "default") = =0) {
        goto found;

    return -ENOENT;

    /* load the module, if this fails, we're doomed, and we should not try * to load a different variant. */
    return load(class_id, path, module);
Copy the code
// hardware/libhardware/hardware.c
/** * Load the file defined by the variant and if successful * return the dlopen handle and the hmi. * @return 0 = success, ! 0 = failure. */
static int load(const char *id,
        const char *path,
        const struct hw_module_t **pHmi)
    int status = -EINVAL;
    void *handle = NULL;
    struct hw_module_t *hmi = NULL;
#ifdef __ANDROID_VNDK__
    const bool try_system = false;
    const bool try_system = true;

    /* * load the symbols resolving undefined symbols before * dlopen returns. Since RTLD_GLOBAL is not or'd in with * RTLD_NOW the external symbols will not be global */
    if (try_system 
        strncmp(path, HAL_LIBRARY_PATH1, strlen(HAL_LIBRARY_PATH1)) == 0) {
        /* If the library is in system partition, no need to check * sphal namespace. Open it with dlopen. */
        handle = dlopen(path, RTLD_NOW);
    } else {
#if defined(__ANDROID_RECOVERY__)
        handle = dlopen(path, RTLD_NOW);
        handle = android_load_sphal_library(path, RTLD_NOW);
    if (handle == NULL) {
        char const *err_str = dlerror();
        ALOGE("load: module=%s\n%s", path, err_str? err_str:"unknown");
        status = -EINVAL;
        goto done;

    /* Get the address of the struct hal_module_info. */
    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
    hmi = (struct hw_module_t *)dlsym(handle, sym);
    if (hmi == NULL) {
        ALOGE("load: couldn't find symbol %s", sym);
        status = -EINVAL;
        goto done;

    /* Check that the id matches */
    if (strcmp(id, hmi-id) ! =0) {
        ALOGE("load: id=%s ! = hmi-id=%s", id, hmi-id);
        status = -EINVAL;
        goto done;

    hmi-dso = handle;

    /* success */
    status = 0;

    if(status ! =0) {
        hmi = NULL;
        if(handle ! =NULL) {
            handle = NULL; }}else {
        ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
                id, path, hmi, handle);

    *pHmi = hmi;

    return status;
Copy the code

Operating above is from the path/system/lib/hw /, / system/lib64 / hw and/vendor/lib64 / hw, / vendor/lib/hw under load so library, the name of a Shared library for MODULE_ID . The variant. So. This SO library is usually the chip vendor's own implementation.

Back to the HIDL_FETCH_IHdmiCec method above, we have the HDMI-CEC module in hand, and now we need to turn on the HDMI-CEC device

static inline int hdmi_cec_open(const struct hw_module_t* module,
        struct hdmi_cec_device** device) {
    return module-methods-open(module,
Copy the code

As you can see from the above implementation, the module-methods-open method is called. This method should be in the hdMI_Crec. variant module, which corresponds to The hw_module_t module can be found by loading hdmi_cec.variable. so. Struct HAL_MODULE_INFO_SYM is a macro definition. The export symbol "HMI" is used to define a struct hw_module_t in a dynamically linked library.

ELF = Executable and Linkable Format as we know ELF = Executable and Linkable Format was developed and published by USL as the Application Binary Interface (ABI) The extension is ELF. An ELF header at the beginning of the file holds a road map describing the organization of the file. Sections hold information about the object file, in connection terms: instructions, data, symbol tables, relocation information, and so on. Our hdMI_Cec.varie. so is an ELF file.

e0004081@user-virtual-machine:~$ file ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[md5/uuid]=11202db35b3b8dc071dae09f1d60c74a, stripped
Copy the code
ubuntu@user-virtual-machine:~$ readelf -s 

Symbol table '.dynsym' contains 26 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize@LIBC (2)
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __android_log_print@LIBLOG (3)
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND __memcpy_chk@LIBC (2)
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@LIBC (2)
     5: 00000000     0 OBJECT  GLOBAL DEFAULT  UND __stack_chk_guard@LIBC (2)
     6: 00000000     0 FUNC    GLOBAL DEFAULT  UND basename@LIBC (2)
     7: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_add_la
     8: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_clear_la
     9: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_close
    10: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_get_connec
    11: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_get_pa
    12: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_get_port_i
    13: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_get_vender
    14: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_get_versio
    15: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_open
    16: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_send_messa
    17: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_set_arc
    18: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_set_event_
    19: 00000000     0 FUNC    GLOBAL DEFAULT  UND eal_hdmirx_cec_set_option
    20: 00000000     0 FUNC    GLOBAL DEFAULT  UND free@LIBC (2)
    21: 00000000     0 FUNC    GLOBAL DEFAULT  UND malloc@LIBC (2)
    22: 00000000     0 FUNC    GLOBAL DEFAULT  UND strcmp@LIBC (2)
    23: 00000000     0 FUNC    GLOBAL DEFAULT  UND memset@LIBC (2)
    24: 00003f4c   128 OBJECT  GLOBAL DEFAULT   20 HMI
    25: 00003fcc     4 OBJECT  GLOBAL DEFAULT   21 es_cec_info
Copy the code

In line 24, the name is HMI, corresponding to the hw_module_T structure.

/** * Name of the hal_module_info */
#define HAL_MODULE_INFO_SYM         HMI
Copy the code

The HAL_MODULE_INFO_SYM below contrasts with the HMI above, and when implementing HAL and creating a module structure, you must name it HAL_MODULE_INFO_SYM.

static struct hw_module_methods_t hdmi_cec_module_methods = {
    .open =  open_cec,

struct hdmi_cec_module HAL_MODULE_INFO_SYM = {
    .common = {
        .tag                = HARDWARE_MODULE_TAG,
        .module_api_version = HDMI_CEC_MODULE_API_VERSION_1_0,
        .hal_api_version    = HARDWARE_HAL_API_VERSION,
        .id                 = HDMI_CEC_HARDWARE_MODULE_ID,
        .name               = "YourCompany hdmi cec Module",
        .author             = "YourCompany Corp.",
        .methods            = hdmi_cec_module_methods,
Copy the code

It should be obvious when you get to this point, and you end up calling the open_CEc method

static int open_cec( const struct hw_module_t* module.char const *name,
        struct hw_device_t **device )
    TVIN_LOGV("enter ");

    if (strcmp(name, HDMI_CEC_HARDWARE_INTERFACE) ! =0) {
        TVIN_LOGE("cec strcmp fail !!!");
        return - 1;

    if (device == NULL) {
        TVIN_LOGE("NULL cec device on open");
        return - 1;

    TV_CEC_INFO_T *dev = (TV_CEC_INFO_T*)malloc(sizeof(*dev));
    if (dev == NULL) {
        TVIN_LOGE("malloc EAL_CEC_INFO_T failed");
        return - 1;

    memset(dev, 0.sizeof(*dev));

    if (dev-fd  0) {
        TVIN_LOGE("can't open CEC Device!!");
        return - 1;

    dev-device.common.tag = HARDWARE_DEVICE_TAG;
    dev-device.common.version = 0;
    dev-device.common.module = (struct hw_module_t*) module;
    dev-device.common.close = cec_close;

    dev-device.add_logical_address      = cec_add_logical_address;
    dev-device.clear_logical_address    = cec_clear_logical_address;
    dev-device.get_physical_address     = cec_get_physical_address;
    dev-device.send_message             = cec_send_message;
    dev-device.register_event_callback  = cec_register_event_callback;
    dev-device.get_version              = cec_get_version;
    dev-device.get_vendor_id            = cec_get_vendor_id;
    dev-device.get_port_info            = cec_get_port_info;
    dev-device.set_option               = cec_set_option;
    dev-device.set_audio_return_channel = cec_set_audio_return_channel;
    dev-device.is_connected             = cec_is_connected;

    *device = dev-device.common;
    es_cec_info = dev;

    return 0;
Copy the code

The code below belongs to the company's private code, sorry, here is not convenient to public, but can introduce a way of thinking, that is to use socket.


This is what Android does with HDMI-CEC support, including the Framework layer and HDMI-CEC HIDL interface. Of course, in previous versions, many vendors have implemented their own proprietary interfaces, but as Android has done a lot of work in this area, It is becoming more and more standardized, and our best way is to rely on the Android native framework to achieve it. This will eliminate a lot of compatibility problems. In addition, hdMI-CEC is something that the average Android developer probably doesn't know much about, and there are few online references. If you happen to be working in this area, I hope this article can help you.

The resources

About me

  • Public id: CodingDev

About (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.