基于Android11,文中涉及到公司的名称用company代替

什么是HDMI-CEC

HDMI-CEC是高清多媒体接口消费类电子产品控制的简称, 它允许多媒体消费类商品相互通信和交换信息,举个例子:家里的电视和电视盒子两个设备通过HDMI线连接,可以让电视盒子的内容显示在电视上。如果两个设置都支持HDMI-CEC可以实现两个设备的相互控制,比如你使用电视盒子遥控器打开盒子,同时电视也会打开,当关闭电视时,电视盒子也会关闭。还有就是使用电视的遥控器可以控制电视盒子。

HDMI-CEC提供哪些功能

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

整体设计

image-20210714194100510.png

Hdmi-Cec配置

Android的Hdmi-Cec代码是可以同时支持Source端和Sink端的,比如TV通过HDMI连接盒子的模式,盒子就是Source端和TV为Sink端。本文以Tv端为主讲述。

以使HdmiControlService正常工作,需要在:

1
device/company/common/products/tv/company_tv.mk

中添加如下配置

1
2
PRODUCT_COPY_FILES += \
 frameworks/native/data/etc/android.hardware.hdmi.cec.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.hdmi.cec.xml

对于机顶盒 (OTT) 等 HDMI 源设备,添加如下:

1
PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=4

对于平板电视等 HDMI 接收设备,添加如下:

1
PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=0

Android为了支持Hdmi-Cec设计了一个系统的Service: HdmiControlService,它负责Hdmi-Cec同应用层和Hal层的连接工作,也就是App通过HdmiControlService提供的接口来与Hdmi-CecHal层进行交互,下面就从HdmiControlService的启动开始.

HdmiControlService启动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# frameworks/base/services/java/com/android/server/SystemServer.java  
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {  
       ...
            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
            //只有配置了 /frameworks/native/data/etc/android.hardware.hdmi.cec,才会走到该逻辑下面
                t.traceBegin("StartHdmiControlService");
                mSystemServiceManager.startService(HdmiControlService.class);
                t.traceEnd();
            }  
       ...
   }

在Java Framework中HdmiControlService负责了HDMI-CEC几乎所有的功能,当然了HdmiControlService中还涉及了MHL相关的功能,这个本文不会涉及。

HdmiControlService#onStart

HdmiControlService作为一个系统服务被启动了,那我们先看一下它做初始化的方法onStart.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//frameworks/base/services/core/java/com/android/server/hdmi/HdmiControlService.java
@Override
    public void onStart() {
        if (mIoLooper == null) {
            mIoThread.start();
            mIoLooper = mIoThread.getLooper();
        }
        //初始化电源状态,默认为POWER_STATUS_TRANSIENT_TO_STANDBY
        mPowerStatus = getInitialPowerStatus();
        mProhibitMode = false;
        //判断档期设备cec开关是否打开
        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
        //判断音量控制cec开关是否打开
        mHdmiCecVolumeControlEnabled = readBooleanSetting(
                Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true);
        mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
                //实例化HdmiCecController ①
        if (mCecController == null) {
            mCecController = HdmiCecController.create(this);
        }
        if (mCecController != null) {
            if (mHdmiControlEnabled) {
                initializeCec(INITIATED_BY_BOOT_UP);
            } else {
                mCecController.setOption(OptionKey.ENABLE_CEC, false);
            }
        } else {
            //如果mCecController为空,则说明这边不支持CEC功能。
            Slog.i(TAG, "Device does not support HDMI-CEC.");
            return;
        }
        if (mMhlController == null) {
            mMhlController = HdmiMhlControllerStub.create(this);
        }
        if (!mMhlController.isReady()) {
            Slog.i(TAG, "Device does not support MHL-control.");
        }
        mMhlDevices = Collections.emptyList();
                //初始化port信息,将cec和mhl的port信息合并起来
        initPortInfo();
        if (mMessageValidator == null) {
            mMessageValidator = new HdmiCecMessageValidator(this);
        }
        //将HdmiControlService添加到ServiceManager
        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
        if (mCecController != null) {
            // Register broadcast receiver for power state change.
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_OFF);
            filter.addAction(Intent.ACTION_SCREEN_ON);
            filter.addAction(Intent.ACTION_SHUTDOWN);
            filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
            getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
            // Register ContentObserver to monitor the settings change.
            registerContentObserver();
        }
        mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
    }

① 实例化HdmiCecController,他的作用是管理CEC命令和行为,并且调用CEC HAL来实现与其他设备的交互。一会我们详细介绍这一块。

HdmiCecController

HdmiCecController是通过下面的方法实例化的

1
HdmiCecController.create(this);
1
2
3
4
//frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecController.java
static HdmiCecController create(HdmiControlService service) {
        return createWithNativeWrapper(service, new NativeWrapperImpl());
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecController.java
/**
     * A factory method with injection of native methods for testing.
     */
    static HdmiCecController createWithNativeWrapper(
            HdmiControlService service, NativeWrapper nativeWrapper) {
        HdmiCecController controller = new HdmiCecController(service, nativeWrapper);
        //连接hdmi-cec hidl接口的server端
        String nativePtr = nativeWrapper.nativeInit();
        if (nativePtr == null) {
            HdmiLogger.warning("Couldn't get tv.cec service.");
            return null;
        }
        controller.init(nativeWrapper);
        return controller;
    }

NativeWrapperImpl

NativeWrapperImpl中调用到了HDMI-CEC的所有HIDL接口

1
String nativePtr = nativeWrapper.nativeInit();
1
2
3
4
5
                @Override
        public String nativeInit() {
          //连接hidl的Server
            return (connectToHal() ? mHdmiCec.toString() : null);
        }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 boolean connectToHal() {
            try {
                //获取HDMI-CEC的实现
                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;
        }

IHdmiCec是一个hidl接口,所有与cec hal层的交互都是通过这个接口实现的,上面的通过IHdmiCec.getService()获取到了IHdmiCec的实现,这样上层就可以通过该HIDL接口,调用HDMI-CEC的实现了。同时上面也是HIDL Client端一个固定的写法。

IHdmiCec.hal接口定义

下面看一下HdmiCec Hal层定义的接口

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//hardware/interfaces/tv/cec/1.0/IHdmiCec.hal

package android.hardware.tv.cec@1.0;

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.
     *
     * @param addr 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
     */
    @callflow(next={"*"})
    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.
     */
    @callflow(next="addLogicalAddress")
    @exit
    clearLogicalAddress();

    /**
     * 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.
     */
    @callflow(next="*")
    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-transmissions' of the CEC Spec 1.4b.
     *
     * @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.
     */
    @callflow(next="*")
    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.
     */
    @callflow(next={"addLogicalAddress"})
    @entry
    setCallback(IHdmiCecCallback callback);

    /**
     * Returns the CEC version supported by underlying hardware.
     *
     * @return version the CEC version supported by underlying hardware.
     */
    @callflow(next={"*"})
    getCecVersion() generates (int32_t version);

    /**
     * Gets the identifier of the vendor.
     *
     * @return vendorId 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.
     */
    @callflow(next={"*"})
    getVendorId() generates (uint32_t vendorId);

    /**
     * Gets the hdmi port information of underlying hardware.
     *
     * @return infos The list of HDMI port information
     */
    @callflow(next={"*"})
    getPortInfo() generates (vec<HdmiPortInfo> 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.
     */
    @callflow(next="*")
    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)
     */
    @callflow(next="*")
    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.
     */
    @callflow(next="*")
    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.
     */
    @callflow(next="*")
    isConnected(int32_t portId) generates (bool status);
};

以上的接口就是HDMI-CEC给上层的Frameworkd提供的所有接口,涉及到的HDMI-CEC的所有功能都是通过这几个接口直接或者间接实现的。

HdmiCec接口实现

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408

#define LOG_TAG "android.hardware.tv.cec@1.0-impl"
#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_cast<int>(CecDeviceType::INACTIVE),
        "CecDeviceType::INACTIVE must match legacy value.");
static_assert(CEC_DEVICE_TV == static_cast<int>(CecDeviceType::TV),
        "CecDeviceType::TV must match legacy value.");
static_assert(CEC_DEVICE_RECORDER == static_cast<int>(CecDeviceType::RECORDER),
        "CecDeviceType::RECORDER must match legacy value.");
static_assert(CEC_DEVICE_TUNER == static_cast<int>(CecDeviceType::TUNER),
        "CecDeviceType::TUNER must match legacy value.");
static_assert(CEC_DEVICE_PLAYBACK == static_cast<int>(CecDeviceType::PLAYBACK),
        "CecDeviceType::PLAYBACK must match legacy value.");
static_assert(CEC_DEVICE_AUDIO_SYSTEM == static_cast<int>(CecDeviceType::AUDIO_SYSTEM),
        "CecDeviceType::AUDIO_SYSTEM must match legacy value.");
static_assert(CEC_DEVICE_MAX == static_cast<int>(CecDeviceType::MAX),
        "CecDeviceType::MAX must match legacy value.");

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

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

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

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

static_assert(HDMI_INPUT == static_cast<int>(HdmiPortType::INPUT),
        "HdmiPortType::INPUT must match legacy value.");
static_assert(HDMI_OUTPUT == static_cast<int>(HdmiPortType::OUTPUT),
        "HdmiPortType::OUTPUT must match legacy value.");

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

sp<IHdmiCecCallback> HdmiCec::mCallback = nullptr;

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

// Methods from ::android::hardware::tv::cec::V1_0::IHdmiCec follow.
Return<Result> HdmiCec::addLogicalAddress(CecLogicalAddress addr) {
    int ret = mDevice->add_logical_address(mDevice, static_cast<cec_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;
        default:
            return Result::FAILURE_UNKNOWN;
    }
}

Return<void> HdmiCec::clearLogicalAddress() {
    mDevice->clear_logical_address(mDevice);
    return Void();
}

Return<void> 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);
            break;
        case -EBADF:
            _hidl_cb(Result::FAILURE_INVALID_STATE, addr);
            break;
        default:
            _hidl_cb(Result::FAILURE_UNKNOWN, addr);
            break;
    }
    return Void();
}

Return<SendMessageResult> HdmiCec::sendMessage(const CecMessage& message) {
    cec_message_t legacyMessage {
        .initiator = static_cast<cec_logical_address_t>(message.initiator),
        .destination = static_cast<cec_logical_address_t>(message.destination),
        .length = message.body.size(),
    };
    for (size_t i = 0; i < message.body.size(); ++i) {
        legacyMessage.body[i] = static_cast<unsigned char>(message.body[i]);
    }
    return static_cast<SendMessageResult>(mDevice->send_message(mDevice, &legacyMessage));
}

Return<void> HdmiCec::setCallback(const sp<IHdmiCecCallback>& callback) {
    if (mCallback != nullptr) {
        mCallback->unlinkToDeath(this);
        mCallback = nullptr;
    }

    if (callback != nullptr) {
        mCallback = callback;
        mCallback->linkToDeath(this, 0 /*cookie*/);
        mDevice->register_event_callback(mDevice, eventCallback, nullptr);
    }
    return Void();
}

Return<int32_t> HdmiCec::getCecVersion() {
    int version;
    mDevice->get_version(mDevice, &version);
    return static_cast<int32_t>(version);
}

Return<uint32_t> HdmiCec::getVendorId() {
    uint32_t vendor_id;
    mDevice->get_vendor_id(mDevice, &vendor_id);
    return vendor_id;
}

Return<void> HdmiCec::getPortInfo(getPortInfo_cb _hidl_cb) {
    struct hdmi_port_info* legacyPorts;
    int numPorts;
    hidl_vec<HdmiPortInfo> portInfos;
    mDevice->get_port_info(mDevice, &legacyPorts, &numPorts);
    portInfos.resize(numPorts);
    for (int i = 0; i < numPorts; ++i) {
        portInfos[i] = {
            .type = static_cast<HdmiPortType>(legacyPorts[i].type),
            .portId = static_cast<uint32_t>(legacyPorts[i].port_id),
            .cecSupported = legacyPorts[i].cec_supported != 0,
            .arcSupported = legacyPorts[i].arc_supported != 0,
            .physicalAddress = legacyPorts[i].physical_address
        };
    }
    _hidl_cb(portInfos);
    return Void();
}

Return<void> HdmiCec::setOption(OptionKey key, bool value) {
    mDevice->set_option(mDevice, static_cast<int>(key), value ? 1 : 0);
    return Void();
}

Return<void> 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();
    }
    const char *languageStr = language.c_str();
    int convertedLanguage = ((languageStr[0] & 0xFF) << 16)
            | ((languageStr[1] & 0xFF) << 8)
            | (languageStr[2] & 0xFF);
    mDevice->set_option(mDevice, HDMI_OPTION_SET_LANG, convertedLanguage);
    return Void();
}

Return<void> HdmiCec::enableAudioReturnChannel(int32_t portId, bool enable) {
    mDevice->set_audio_return_channel(mDevice, portId, enable ? 1 : 0);
    return Void();
}

Return<bool> 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

HIDL的类型分为两种:Binderized和Passthrough,Binderized在不同的进程中(Impl和service)而Passthrough则在同一个进程中。如果使用Passthrough,需要在Impl中暴露HIDL_FETCH_*接口,例如上面代码中的HIDL_FETCH_IHdmiCec接口。在HIDL_FETCH_IHdmiCec方法中,通过hw_get_module获取对应的硬件模块,而hw_get_module获取对应模块是根据模块ID,例如这里的HDMI_CEC_HARDWARE_MODULE_ID.

1
2
3
4
5
// 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);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 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);
    else
        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) {
            goto found;
        }
    }

    /* Loop through the configuration variants looking for a module */
    for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
        if (property_get(variant_keys[i], prop, NULL) == 0) {
            continue;
        }
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }

    /* Nothing found, try the default */
    if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
        goto found;
    }

    return -ENOENT;

found:
    /* 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);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 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;
#else
    const bool try_system = true;
#endif

    /*
     * 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);
#else
        handle = android_load_sphal_library(path, RTLD_NOW);
#endif
    }
    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;

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

    *pHmi = hmi;

    return status;
}

上面的操作就是从路径/system/lib/hw/, /system/lib64/hw/vendor/lib64/hw,/vendor/lib/hw下加载so库,共享库的名称为<MODULE_ID>.variant.so. 这个so库一般是芯片厂商自己的实现.

回到上面的HIDL_FETCH_IHdmiCec方法,HDMI-CEC的模块拿到了,现在就要打开HDMI-CEC的设备

1
2
3
4
5
static inline int hdmi_cec_open(const struct hw_module_t* module,
        struct hdmi_cec_device** device) {
    return module->methods->open(module,
            HDMI_CEC_HARDWARE_INTERFACE, TO_HW_DEVICE_T_OPEN(device));
}

从上面的实现可以看到调用的是module->methods->open方法,这个方法应该在hdmi_cec.variant模块里,该模块对应的是上面的hdmi_cec.variant.so.前面说了,通过load hdmi_cec.variant.so这个so库就可以找到hw_module_t这个module,那他是怎么找到的,先看一下下面的代码定义的一个struct HAL_MODULE_INFO_SYM,并且HAL_MODULE_INFO_SYM是个宏定义,为什么根据“HMI”这个导出符号,就可以从动态链接库中找到结构体hw_module_t呢?

我们知道,ELF = Executable and Linkable Format,可执行连接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的,扩展名为elf。一个ELF头在文件的开始,保存了路线图(road map),描述了该文件的组织情况。sections保存着object 文件的信息,从连接角度看:包括指令,数据,符号表,重定位信息等等。我们的hdmi_cec.variant.so就是一个elf格式的文件。

1
2
e0004081@user-virtual-machine:~$ file hdmi_cec.variant.so
hdmi_cec.variant.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[md5/uuid]=11202db35b3b8dc071dae09f1d60c74a, stripped
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ubuntu@user-virtual-machine:~$ readelf -s hdmi_cec.variant.so

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

第24行,名字就是HMI,对应于hw_module_t结构体。

1
2
3
4
/**
 * Name of the hal_module_info
 */
#define HAL_MODULE_INFO_SYM         HMI

下面的HAL_MODULE_INFO_SYM对照的就是上面的HMI,实现 HAL 并创建模块结构体时,您必须将其命名为 HAL_MODULE_INFO_SYM

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//vendor/company/proprietary/hardware/hidl_impl/tv/cec/tv_cec_hw.cpp
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,
    },
};

走到这里应该豁然开朗了,最后调用的是open_cec方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//vendor/company/proprietary/hardware/hidl_impl/tv/cec/tv_cec_hw.cpp
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));
    eal_hdmirx_cec_open();
    eal_hdmirx_cec_set_event_observer(cec_event_update);

    if (dev->fd < 0) {
        TVIN_LOGE("can't open CEC Device!!");
        free(dev);
        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;
}

再往下的代码就属于公司的私有代码了,很抱歉,这里不方便公开,但是可以介绍一种思路,那就是使用socket的方式。

总结

上面就是Android对Hdmi-Cec支持所做的工作,包括了Framework层和Hdmi-Cec HIDL接口,当然了之前的一些版本,有很多厂商使用自己的私有接口的方式实现的,但是随着Android在这方面做了很多的工作,也越来越规范化,我们最好的方式还是依赖Android原生的框架去实现。这样会杜绝很多兼容上面的问题。另外关于Hdmi-Cec这块,普通的Android开发者可能很少接触,网上的介绍也很少,如果你正好在做这方面的工作,也希望本文能帮到你^v^.

参考资料

关于我

  • 公众号: CodingDev