前言
- 以下分析基于android14
- CEC(Consumer Electronics Control)信号通过13引脚传输,作为HDMI接口的一部分。CEC总线作为控制信号被分离出来,使得在不增加数据占用宽带的情况下完成高速复杂的通信要求
- 这优先分析一下,是怎么和cec设备通信的,也就是收发的过程
HdmiControlService
frameworks/base/services/core/java/com/android/server/hdmi/HdmiControlService.java
cec的相关内容都在HdmiControlService里具体实现
在frameworks/base/core/java/android/content/Context.java里查看
1
2
3
4
5
6
7
8
9
10
11
/**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.hardware.hdmi.HdmiControlManager} for controlling and managing
* HDMI-CEC protocol.
*
* @see #getSystemService(String)
* @see android.hardware.hdmi.HdmiControlManager
* @hide
*/
@SystemApi
public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
frameworks/base/core/java/android/hardware/hdmi/HdmiControlManager.java
接口在通过HdmiControlManager调用到HdmiControlService
frameworks/base/core/java/android/app/SystemServiceRegistry.java 在这里注册了服务
1
2
3
4
5
6
7
registerService(Context.HDMI_CONTROL_SERVICE, HdmiControlManager.class,
new StaticServiceFetcher<HdmiControlManager>() {
@Override
public HdmiControlManager createService() throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.HDMI_CONTROL_SERVICE);
return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b));
}});
HdmiCecController
frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecController.java
- 在这个类中,定义了cec操作的接口和实现,并做了对不同版本hal层兼容
- HdmiControlService中的实现就是调用HdmiCecController里的接口,在HdmiControlService的调用如下:
1
2
3
4
5
6
7
8
9
10
11
void initService() {
//省略
if (mCecController == null) {
mCecController = HdmiCecController.create(this, getAtomWriter());
}
if (mCecController == null) {
Slog.i(TAG, "Device does not support HDMI-CEC.");
return;
}
//省略
}
看create方法里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) {
HdmiCecController controller =
createWithNativeWrapper(service, new NativeWrapperImplAidl(), atomWriter);
if (controller != null) {
return controller;
}
HdmiLogger.warning("Unable to use CEC and HDMI Connection AIDL HALs");
controller = createWithNativeWrapper(service, new NativeWrapperImpl11(), atomWriter);
if (controller != null) {
return controller;
}
HdmiLogger.warning("Unable to use cec@1.1");
return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter);
}
可以看到这里对连接不同hal版本做了兼容
- NativeWrapperImplAidl是默认的版本,目前最新的版本是hal层可以通过aidl直接通到java
- NativeWrapperImpl11是hal层实现的v1.1版本
- NativeWrapperImpl是hal层实现的v1.0版本
在目前14版本下,会直接走NativeWrapperImplAidl,如下(后面的具体接口分析都是看NativeWrapperImplAidl)
1
2
3
4
5
6
7
private static final class NativeWrapperImplAidl
implements NativeWrapper, IBinder.DeathRecipient {
private IHdmiCec mHdmiCec;
private IHdmiConnection mHdmiConnection;
@Nullable private HdmiCecCallback mCallback;
//省略
}
NativeWrapper是接口定义,关键的就是初始化,发送指令,接受指令
1
2
3
4
5
6
protected interface NativeWrapper {
String nativeInit();
void setCallback(HdmiCecCallback callback);
int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body);
//省略
}
初始化
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
@Override
public String nativeInit() {
return connectToHal() ? mHdmiCec.toString() + " " + mHdmiConnection.toString() : null;
}
boolean connectToHal() {
mHdmiCec =
IHdmiCec.Stub.asInterface(
ServiceManager.getService(IHdmiCec.DESCRIPTOR + "/default"));
if (mHdmiCec == null) {
HdmiLogger.error("Could not initialize HDMI CEC AIDL HAL");
return false;
}
try {
mHdmiCec.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
HdmiLogger.error("Couldn't link to death : ", e);
}
mHdmiConnection =
IHdmiConnection.Stub.asInterface(
ServiceManager.getService(IHdmiConnection.DESCRIPTOR + "/default"));
if (mHdmiConnection == null) {
HdmiLogger.error("Could not initialize HDMI Connection AIDL HAL");
return false;
}
try {
mHdmiConnection.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
HdmiLogger.error("Couldn't link to death : ", e);
}
return true;
}
mHdmiCec这里是获取到了hal层服务android.hardware.tv.hdmi.cec.IHdmiCec/default
IHdmiCec是hardware/interfaces/tv/hdmi/cec/aidl/android/hardware/tv/hdmi/cec/IHdmiCec.aidl生成的代码,在out下,并且在附近还有jar包
./soong/.intermediates/hardware/interfaces/tv/hdmi/cec/aidl/android.hardware.tv.hdmi.cec-V1-java-source/gen/android/hardware/tv/hdmi/cec/IHdmiCec.java
./soong/.intermediates/hardware/interfaces/tv/cec/1.0/android.hardware.tv.cec-V1.0-java_gen_java/gen/srcs/android/hardware/tv/cec/V1_0/IHdmiCec.java
./soong/.intermediates/hardware/interfaces/tv/cec/1.1/android.hardware.tv.cec-V1.1-java_gen_java/gen/srcs/android/hardware/tv/cec/V1_1/IHdmiCec.java
mHdmiConnection的作用是判断cec的设备的插拔监听,获取hal层服务android.hardware.tv.hdmi.connection.IHdmiConnection/default
IHdmiConnection是通过
hardware/interfaces/tv/hdmi/connection/aidl/android/hardware/tv/hdmi/connection/IHdmiConnection.aidl生成的代码,在out下
./soong/.intermediates/hardware/interfaces/tv/hdmi/connection/aidl/android.hardware.tv.hdmi.connection-V1-java-source/gen/android/hardware/tv/hdmi/connection/IHdmiConnection.java
发送指令
这是在NativeWrapperImplAidl里的nativeSendCecCommand实现
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
CecMessage message = new CecMessage();
message.initiator = (byte) (srcAddress & 0xF);
message.destination = (byte) (dstAddress & 0xF);
message.body = body;
try {
return mHdmiCec.sendMessage(message);
} catch (RemoteException e) {
HdmiLogger.error("Failed to send CEC message : ", e);
return SendMessageResult.FAIL;
}
}
这里发送指令,就是把源地址,目前地址,数据,转成CecMessage,打包传到hal层处理
在HdmiControlService中调用HdmiCecController中的sendCommand
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
void sendCommand(final HdmiCecMessage cecMessage,
final HdmiControlService.SendMessageCallback callback) {
assertRunOnServiceThread();
List<String> sendResults = new ArrayList<>();
runOnIoThread(new Runnable() {
@Override
public void run() {
HdmiLogger.debug("[S]:" + cecMessage);
byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
int retransmissionCount = 0;
int errorCode = SendMessageResult.SUCCESS;
do {
errorCode = mNativeWrapperImpl.nativeSendCecCommand(
cecMessage.getSource(), cecMessage.getDestination(), body);
switch (errorCode) {
case SendMessageResult.SUCCESS: sendResults.add("ACK"); break;
case SendMessageResult.FAIL: sendResults.add("FAIL"); break;
case SendMessageResult.NACK: sendResults.add("NACK"); break;
case SendMessageResult.BUSY: sendResults.add("BUSY"); break;
}
if (errorCode == SendMessageResult.SUCCESS) {
break;
}
} while (retransmissionCount++ < HdmiConfig.RETRANSMISSION_COUNT);
final int finalError = errorCode;
if (finalError != SendMessageResult.SUCCESS) {
Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError);
}
runOnServiceThread(new Runnable() {
@Override
public void run() {
mHdmiCecAtomWriter.messageReported(
cecMessage,
FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED__DIRECTION__OUTGOING,
getCallingUid(),
finalError
);
if (callback != null) {
callback.onSendCompleted(finalError);
}
}
});
}
});
addCecMessageToHistory(false /* isReceived */, cecMessage, sendResults);
}
这sendCommand里主要就是把操作码和参数拼凑成body,调用nativeSendCecCommand,并且把这次发送的记录保存起来,在dumpsys的时候能看到
接受指令
这里是在NativeWrapperImplAidl的setCallback实现,这里HdmiCecCallbackAidl继承了 IHdmiCecCallback.Stub
这个也是hardware/interfaces/tv/hdmi/cec/aidl/android/hardware/tv/hdmi/cec/IHdmiCecCallback.aidl所生成的代码,把上层的callback传递到hal层,在hal层有读到数据后就上报
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void setCallback(HdmiCecCallback callback) {
mCallback = callback;
try {
// Create an AIDL callback that can callback onCecMessage
mHdmiCec.setCallback(new HdmiCecCallbackAidl(callback));
} catch (RemoteException e) {
HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
}
try {
// Create an AIDL callback that can callback onHotplugEvent
mHdmiConnection.setCallback(new HdmiConnectionCallbackAidl(callback));
} catch (RemoteException e) {
HdmiLogger.error("Couldn't initialise tv.hdmi callback : ", e);
}
}
HdmiCecCallback也是HdmiCecController的内部类
1
2
3
4
5
6
7
8
9
10
11
12
final class HdmiCecCallback {
@VisibleForTesting
public void onCecMessage(int initiator, int destination, byte[] body) {
runOnServiceThread(
() -> handleIncomingCecCommand(initiator, destination, body));
}
@VisibleForTesting
public void onHotplugEvent(int portId, boolean connected) {
runOnServiceThread(() -> handleHotplug(portId, connected));
}
}
这个在init设置到callback
1
2
3
4
5
private void init(NativeWrapper nativeWrapper) {
mIoHandler = new Handler(mService.getIoLooper());
mControlHandler = new Handler(mService.getServiceLooper());
nativeWrapper.setCallback(new HdmiCecCallback());
}
在底层上报后在handleIncomingCecCommand里,就是把上报的数据拼装成HdmiCecMessage,加入记录保存,并onReceiveCommand
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
assertRunOnServiceThread();
if (body.length == 0) {
Slog.e(TAG, "Message with empty body received.");
return;
}
HdmiCecMessage command = HdmiCecMessage.build(srcAddress, dstAddress, body[0],
Arrays.copyOfRange(body, 1, body.length));
if (command.getValidationResult() != HdmiCecMessageValidator.OK) {
Slog.e(TAG, "Invalid message received: " + command);
}
HdmiLogger.debug("[R]:" + command);
addCecMessageToHistory(true /* isReceived */, command, null);
mHdmiCecAtomWriter.messageReported(command,
incomingMessageDirection(srcAddress, dstAddress), getCallingUid());
onReceiveCommand(command);
}
通过onReceiveCommand给HdmiControlService上报
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
void onReceiveCommand(HdmiCecMessage message) {
assertRunOnServiceThread();
if (((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_IGNORE) == 0)
&& !mService.isCecControlEnabled()
&& !HdmiCecMessage.isCecTransportMessage(message.getOpcode())) {
if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_LOG_WARNING) != 0) {
HdmiLogger.warning("Message " + message + " received when cec disabled");
}
if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_DROP_MSG) != 0) {
return;
}
}
if (mService.isAddressAllocated() && !isAcceptableAddress(message.getDestination())) {
return;
}
//这里就是上报操作
@Constants.HandleMessageResult int messageState = mService.handleCecCommand(message);
if (messageState == Constants.NOT_HANDLED) {
// Message was not handled
maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
} else if (messageState != Constants.HANDLED) {
// Message handler wants to send a feature abort
maySendFeatureAbortCommand(message, messageState);
}
}
在HdmiControlService收到上报的HdmiCecMessage后,会通过HdmiCecNetwork处理信息,
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
protected int handleCecCommand(HdmiCecMessage message) {
assertRunOnServiceThread();
@HdmiCecMessageValidator.ValidationResult
int validationResult = message.getValidationResult();
if (validationResult == HdmiCecMessageValidator.ERROR_PARAMETER
|| validationResult == HdmiCecMessageValidator.ERROR_PARAMETER_LONG
|| !verifyPhysicalAddresses(message)) {
return Constants.ABORT_INVALID_OPERAND;
} else if (validationResult != HdmiCecMessageValidator.OK
|| sourceAddressIsLocal(message)) {
return Constants.HANDLED;
}
//这里处理信息,例如,新增cec设备,或者已经有的cec处理操作码
getHdmiCecNetwork().handleCecMessage(message);
@Constants.HandleMessageResult int handleMessageResult =
dispatchMessageToLocalDevice(message);
// mAddressAllocated is false during address allocation, meaning there is no device to
// handle the message, so it should be buffered, if possible.
if (!mAddressAllocated
&& mCecMessageBuffer.bufferMessage(message)) {
return Constants.HANDLED;
}
return handleMessageResult;
}