Android Hdmi-CEC 相关文档
A.Android中的HDMI-CEC 背景介绍
B.Android 按键处理流程和HDMI-CEC按键指令的流程分析
C.HDMI-CEC 指令One Touch Play 代码举例
A.android 中HDMI-CEC背景介绍
HDMI-CEC(高清晰度多媒体接口的消费电子控制标准)允许多媒体消费类产品之间沟通和交换信息。HDMI-CEC支持多种功能,例如直通遥控,系统音频控制,其中最常用的是一键播放功能。一键播放功能是指媒体源设备能够打开电视并且让电视自动切换到自己的端口进行播放,这样的话用户就不需要远程控制电视从Chromecast切换到蓝光播放器。
很多厂商采用的HDMI-CEC标准,使他们的产品能够和其他厂商的产品相互工作。但是由于每个厂商实现HDMI-CEC标准的方式不同。很多时候这样的设备之间不能相互理解,而且这些设备还支持不同的功能。由于这种情况,消费者不能简单的认为声称支持CEC的产品之间能够兼容使用。
为了缓解以上描述的设备不能够进行很好的兼容的问题,随着android TIF的引入,HDMI-CEC 能够让相互连接的设备沟通起来,也就是说能够很好的降低设备之间的兼容性问题。Android创建了一个系统服务HdmiControlSerivce来有效缓解这个问题。
HdmiControlSerivce是和系统其他的部分(TIF,Audio服务,电源管理服务)一起来实现CEC标准。
我们从下面两幅图来理解HDMIControlSerivce是怎么缓解兼容性问题的;
图一可以看出,在android5.0之前没有HDMIControlService,对不同设备的兼容和控制都是通过Custom CEC Controller模块来完成(这个可能是厂商自己开发的,或者用第三方的方案),这样所有的兼容性处理工作都放在了Custom CEC Controller模块,里面涉及到显示操作,Audiod 的控制,power的控制,都要通过其来管理,这样使兼容性问题处理起来显得异常复杂,不同设备一多起来,就对Custom CEC Controller模块的健壮性,和扩展性等提高了要求。Android为了解决这个问题(因为google 牛B,所以他才搞),就设计了基于HDMI Control Service服务为核心的一套逻辑,请看图2。从图2中可以看出,HDMI Control Service服务下面添加了HDMI CEC HAL层,这个HAL 层,就是各个使用厂商需要着力开发的部分,里面有一套接口,需要android厂商遵守实现,这样减少了兼容问题的复杂性,只需要在HAL层实现一套接口(下层驱动实现)(接口的定义请看官方文档:https://source.android.google.cn/devices/tv/hdmi-cec),接口里面实现兼容各个HDMI Source设备的指令协议。(仍然是要兼容,但是整体处理兼容问题的工作变得更加公开和便捷,收敛到driver层来解决问题。)而HDMI Control Service服务本身在framework层,可以通过android系统已有的进程间通信机制与TIF ,Audio Service,Power Manager Service进行通信。大大提升了设备之间交互的稳定性等。
B.Android 按键处理流程和HDMI-CEC按键指令的流程分析
This diagram below shows how buttons on a remote control are passed to a specific TV Input for picture in picture (PIP) display. Those button presses are interpreted by the hardware driver supplied by the device manufacturer,converting hardware scancode to Android keycodes and passing them to the standard Android input pipeline ,InputReader and InputDispatcher functions as KeyEvents.These in turn trigger events on the TV App if it is in focus. PhoneWindowManager接受到这些events后,就dispatch to application. Application 收到相关event后,会有对应的处理逻辑,通过对应的处理逻辑调用函数会执行到下层,进而实现整个event的功能。
1.1下面给出一个例子具体来说明keyEvent是如何被分发的.
上图描述了红键应用的流程,这是欧洲常见的比如MHEG-5(类似于HBBTV)里面应用到的按键(红键)的交互功能。这个应用可以在电视流媒体中传输。当这个键被按下后,可以让用户和这些广播应用交互。例如,你可以用这些广播应用来获取相关网页和比赛结果.
在这个示图中:
注意:第三方的TV inputs不能接收按键事件(只有当前的应用才有接收InputEvents的能力)。
HDMI-CEC允许一个设备来控制其他的设备,这样的话就能够用一个遥控器来控制家庭中的多个设备。这个功能被AndroidTV用来通过集中的TV APP 来加速配置和远程控制各个TV Inputs,例如,可以切换输入源,开关设备等操作。
AndroidTIF 将HDMI-CEC实现为HDMI Control Service.这样的话电视制造商只需要开发底层的驱动,并且和轻量级的androidTV 硬件抽象层交互,不需要关注的复杂的业务层逻辑。为了提供一个标准实现,android通过减少碎片化支持可选择的功能来减少兼容性问题。HDMI Control Service是基于已有的android Service,包括输入和开关机功能。
这意味着现有的HDMI-CEC实现需要重新设计来适配Android TIF,我们推荐在硬件上包含一个微处理器来接受CEC开机指令和其他的命令(这意味着在待机状态有一个微处理芯片在监听CEC设备,如果CEC设备发出指令,微处理器就会和主芯片进行通信,比如唤醒TV)。
C.HDMI-CEC 指令One Touch Play 代码举例 (在线UML画图邀请链接:https://www.processon.com/i/5e7ec323e4b092510f7ccbb7)
代码架构图
下面两个图以HdmiControlService 为衔接点,上图是说明android HDMI framework对HDMI action的一些封装。下图是Android hdmi framwork,,MTK hdmi framework,以及livetv的衔接情况。
里面跨进程(MTK 的AbstractHdmiTvInputServiceBase进程和HDMIControlService进程)通信衔接利用的方法是onChanged方法.
Android Hdmi framework中 HdmiControlSerivce.java
MTK HDMI framework 层实现的AbstractHdmiTvInputServiceBase.java方法
private final class HdmiInputChangeListener implements InputChangeListener {
@Override
public void onChanged(final HdmiDeviceInfo device) {
runOnServiceThread(new Runnable() {
@Override
public void run() {
if (TvInputConst.DEBUG) {
Log.d(TAG, "HdmiInputChangeListener, onChanged, device = " + device.toString());
}
String inputId = findInputIdForDeviceInfo(device);
if (inputId == INVALID_INPUT_ID) {
if (TvInputConst.DEBUG) {
Log.e(TAG, "HdmiInputChangeListener, "
+ "onChanged, inputId == INVALID_INPUT_ID, return");
}
return;
}
((HdmiServiceHandler) mHandler).clearTimer(TIMER_DEVICE_SELECT);
if (inputId.equals(mInputContext.inputId)) {
if (TvInputConst.DEBUG) {
Log.d(TAG, "HdmiInputChangeListener, "
+ "onChanged, inputId == (mInputContext.inputId)");
}
AbstractHdmiTvInputSessionImpl session = getCurrentSession(inputId);
// Set the surface deferred from the actual onSetSurface now.
if (session != null) {
/*
* if (hasDeferredSurface(mInputContext)) { if (TvInputConst.DEBUG) { Log.d(TAG,
* "HdmiInputChangeListener, " + "onChanged, (inputId == (mInputContext.inputId)), " +
* "hasDeferredSurface call setSurface"); } session.setSurface(mInputContext.surface);
* }
*/
if (TvInputConst.DEBUG) {
Log.d(TAG, "HdmiInputChangeListener, "
+ "onChanged, (inputId == (mInputContext.inputId))");
}
}
} else {
// A new input change request overwriting the one being anticipated, or
// we were not watching HDMI at all. Request a new input selection to app.
Log.d(TAG, "Tis don't handle one touch play");
if (TvInputConst.DEBUG) {
Log.d(TAG, "HdmiInputChangeListener, "
+ "onChanged, (inputId != (mInputContext.inputId)) ");
}
if (MtkTvScan.getInstance().isScanning()) {
if (TvInputConst.DEBUG) {
Log.d(TAG, "HdmiInputChangeListener"
+ " onChanged: (inputId != (mInputContext.inputId)) isScanning is true, return");
}
return;
}
if (Settings.Secure.getInt(AbstractHdmiTvInputServiceBase.this.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0) == 0) {
if (TvInputConst.DEBUG) {
Log.d(TAG, "HdmiInputChangeListener, Ignoring notification: setup not complete");
}
return;
}
mInputContext.reset();
requestInputChange(inputId);
mInputContext.inputId = inputId;
}
}
});
}
private void requestInputChange(String inputId) {
//Log.d(TAG, "requestInputChange=============");
//Intent intent = new Intent(Intent.ACTION_VIEW);
Intent intent = new Intent();
ComponentName componentName = new ComponentName("skyworth.skyworthlivetv","skyworth.skyworthlivetv.osd.ui.mainActivity.LiveTvScreenActivity");
if (isOldProduct()) {
componentName = new ComponentName("skyworth.skyworthlivetv","skyworth.skyworthlivetv.osd.ui.mainActivity.LiveTvScreenActivity");
}else{
componentName = new ComponentName("com.smartdevice.livetv","skyworth.skyworthlivetv.osd.ui.mainActivity.LiveTvScreenActivity");
}
intent.setComponent(componentName);
intent.setAction(Intent.ACTION_VIEW);
intent.setData(TvContract.buildChannelUriForPassthroughInput(inputId));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("livetv", true);
startActivity(intent);
}}