Android上的HDMI CEC [英] HDMI CEC on android
问题描述
我在访问我正在尝试打开电视并更改电视的输入源,但是我无法打开电视.
I'm trying to turn on the tv and change the input source of the tv but I was unable to it.
Android API方法
我正在运行系统应用程序,并且已经解决
I'm running a system app and I have settled
<uses-permission android:name="android.permission.HDMI_CEC" />
在AndroidManifest.xml上.
on AndroidManifest.xml.
我无法通过反射访问HDMI服务,因为即使是系统应用程序也无法直接访问它.
I'm accessing to HDMI service through reflection since I was not able to access it directly, even being a system app.
public class HdmiHelper {
public HdmiHelper(Context context) {
init(context);
}
public void init(Context context) {
try {
//Interface Callback Proxy
Class<?> hotplugEventListenerClass = Class.forName("android.hardware.hdmi.HdmiControlManager$HotplugEventListener");
Class<?> vendorCommandListenerClass = Class.forName("android.hardware.hdmi.HdmiControlManager$VendorCommandListener");
Class<?> oneTouchPlayCallbackClass = Class.forName("android.hardware.hdmi.HdmiPlaybackClient$OneTouchPlayCallback");
Class<?> displayStatusCallbackClass = Class.forName("android.hardware.hdmi.HdmiPlaybackClient$DisplayStatusCallback");
Object interfaceOneTouchPlaybackCallback = Proxy.newProxyInstance(oneTouchPlayCallbackClass.getClassLoader(),
new Class<?>[]{ oneTouchPlayCallbackClass } , new callbackProxyListener() );
Object interfaceHotplugEventCallback = Proxy.newProxyInstance(hotplugEventListenerClass.getClassLoader(),
new Class<?>[]{ hotplugEventListenerClass } , new callbackProxyListener() );
Object interfaceDisplayStatusCallbackClass = Proxy.newProxyInstance(displayStatusCallbackClass.getClassLoader(),
new Class<?>[]{ displayStatusCallbackClass } , new callbackProxyListener() );
Method m = context.getClass().getMethod("getSystemService", String.class);
Object obj_HdmiControlManager = m.invoke(context, (Object) "hdmi_control");
Log.d("HdmiHelper", "obj: " + obj_HdmiControlManager + " | " + obj_HdmiControlManager.getClass());
for( Method method : obj_HdmiControlManager.getClass().getMethods()) {
Log.d("HdmiHelper", " method: " + method.getName() );
}
Method method_addHotplugEventListener = obj_HdmiControlManager.getClass().getMethod("addHotplugEventListener", hotplugEventListenerClass);
method_addHotplugEventListener.invoke(obj_HdmiControlManager, interfaceHotplugEventCallback);
Method m2 = obj_HdmiControlManager.getClass().getMethod("getPlaybackClient");
Object obj_HdmiPlaybackClient = m2.invoke( obj_HdmiControlManager );
Log.d("HdmiHelper", "obj_HdmiPlaybackClient: " + obj_HdmiPlaybackClient + " | " + obj_HdmiPlaybackClient.getClass());
Method method_oneTouchPlay = obj_HdmiPlaybackClient.getClass().getMethod("oneTouchPlay", oneTouchPlayCallbackClass);
method_oneTouchPlay.invoke( obj_HdmiPlaybackClient, interfaceOneTouchPlaybackCallback);
Method method_queryDisplayStatus = obj_HdmiPlaybackClient.getClass().getMethod("queryDisplayStatus", displayStatusCallbackClass);
method_queryDisplayStatus.invoke( obj_HdmiPlaybackClient, interfaceDisplayStatusCallbackClass);
Method method_getActiveSource = obj_HdmiPlaybackClient.getClass().getMethod("getActiveSource");
Log.d("HdmiHelper", "getActiveSource: " + method_getActiveSource.invoke(obj_HdmiPlaybackClient));
}catch (Exception e) {
e.printStackTrace();
}
}
public class callbackProxyListener implements java.lang.reflect.InvocationHandler {
public callbackProxyListener() {
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Log.d("HdmiHelper", "Start method " + method.getName() + " | " + proxy.getClass() + " | " + method.getDeclaringClass() );
if ( args != null ) {
// Prints the method being invoked
for (int i = 0; i != args.length; i++) {
Log.d("HdmiHelper", " - Arg(" + i + "): " + args[i].toString());
}
}
if (method.getName().equals("onReceived")) {
if (args.length == 1) {
onReceived(args[0]);
}else
if (args.length == 3) {
onReceived( (int) args[0], BytesUtil.toByteArray( args[1] ), (boolean) args[2] );
}
}else
if (method.getName().equals("onComplete")) {
onComplete( (int) args[0] );
}else
if (method.getName().equals("toString")) {
return this.toString();
}else {
return method.invoke(this, args);
}
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
void onComplete(int result) {
Log.d("HdmiHelper", "onComplete: " + result);
}
void onReceived(Object event) {
Class eventClass = event.getClass();
Log.d("HdmiHelper", "onReceived(1): " + event.toString() + " | " + eventClass);
try {
Method method_getPort = eventClass.getMethod("getPort");
Method method_isConnected = eventClass.getMethod("isConnected");
Method method_describeContents = eventClass.getMethod("describeContents");
Log.d("HdmiHelper", " - " + method_getPort.invoke(event) + " | " + method_isConnected.invoke(event) + " | " + method_describeContents.invoke(event) );
}catch (Exception e) {
e.printStackTrace();
}
}
void onReceived(int srcAddress, byte[] params, boolean hasVendorId) {
Log.d("HdmiHelper", "onReceived(3): " + srcAddress + " | " + params + " | " + hasVendorId);
}
}
记录答案:
D/HdmiHelper: obj: android.hardware.hdmi.HdmiControlManager@7bca63c | class android.hardware.hdmi.HdmiControlManager
D/HdmiHelper: obj_HdmiPlaybackClient: android.hardware.hdmi.HdmiPlaybackClient@6345d1a | class android.hardware.hdmi.HdmiPlaybackClient
D/HdmiHelper: Start method onReceived | class $Proxy2 | interface android.hardware.hdmi.HdmiControlManager$HotplugEventListener
D/HdmiHelper: onReceived(1): android.hardware.hdmi.HdmiHotplugEvent@4c5c04b | class android.hardware.hdmi.HdmiHotplugEvent
D/HdmiHelper: - 1 | true | 0
- 问题1:
- 问题2:
我收到了正确的消息:这意味着电视是正确的.如果电视关闭,我会收到错误消息.似乎可行.
I received true: which means that tv is on what is true. If the tv is off i receive false. That seems to work.
尽管如此,我期望每次更改电视状态时都会收到回调,但这种情况没有发生.有什么主意吗?
Though, I was expecting to receive a callback every time I change the tv state, which is not happening. Any idea?
继续OneTouchPlayCallback的日志:
continuing with the logs for the OneTouchPlayCallback:
D/HdmiHelper: Start method onComplete | class $Proxy1 | interface android.hardware.hdmi.HdmiPlaybackClient$OneTouchPlayCallback
D/HdmiHelper: onComplete: 2
查看类 HdmiControlManager.java class}.我收到了2,我认为这是RESULT_SOURCE_NOT_AVAILABLE.
Looking into the class HdmiPlaybackClient.java if everything went good the answer would be 0 (@param result the result of the operation. {@link HdmiControlManager#RESULT_SUCCESS . You can find this variable in HdmiControlManager.java class}. Instead, I receive 2 which i assume that is RESULT_SOURCE_NOT_AVAILABLE.
知道为什么吗?
- 问题3
现在继续显示DisplayStatusCallback的日志:
Continuing now with the logs for the DisplayStatusCallback:
D/HdmiHelper: Start method onComplete | class $Proxy3 | interface android.hardware.hdmi.HdmiPlaybackClient$DisplayStatusCallback
D/HdmiHelper: onComplete: 2
根据此回调的定义:
/**
* Listener used by the client to get display device status.
*/
public interface DisplayStatusCallback {
/**
* Called when display device status is reported.
*
* @param status display device status. It should be one of the following values.
* <ul>
* <li>{@link HdmiControlManager#POWER_STATUS_ON}
* <li>{@link HdmiControlManager#POWER_STATUS_STANDBY}
* <li>{@link HdmiControlManager#POWER_STATUS_TRANSIENT_TO_ON}
* <li>{@link HdmiControlManager#POWER_STATUS_TRANSIENT_TO_STANDBY}
* <li>{@link HdmiControlManager#POWER_STATUS_UNKNOWN}
* </ul>
*/
public void onComplete(int status);
}
并查看HdmiControlManager,我收到2表示意味着:
and looking into the HdmiControlManager I receive 2 wich means that is:
public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
这是一个奇怪的结果,因为那不是正在发生的事情.
Which is a strange result because that is not what's happening.
- 继续查看日志以获取您的信息:
答案:
getActiveSource为空
getActiveSource is null
我还测试了调用getTvClient()方法的这段代码:
I also tested this code that calls the getTvClient() method:
Method method_getTvClient = obj_HdmiControlManager.getClass().getMethod("getTvClient");
Object obj_HdmiTvClient = method_getTvClient.invoke( obj_HdmiControlManager );
Log.d("HdmiHelper", "obj_HdmiTvClient: " + obj_HdmiTvClient);
,结果为空.
我还尝试了在 CEC-O-MATIC网站之后发送供应商命令的方法,但是我无法成功.如果您对此有任何说明,请给我一些指导,我会对其进行测试.
I also tried the approach of sending a vendor command following CEC-O-MATIC website but I was unable to have success. If you have any instructions about this, please give me some directions and I will test it.
LibCEC方法:
由于这个,我能够将libcec交叉编译到android发布.但是libcec总是回答我控制器未确认命令'PING'".
I was able to cross compile libcec to android thanks to this post. But libcec is always answering to me "command 'PING' was not acked by the controller".
我已将标志-DHAVE_EXYNOS_API = 1和-DHAVE_AOCEC_API = 1添加到libcec.
I've added the flags -DHAVE_EXYNOS_API=1 and -DHAVE_AOCEC_API=1 to libcec.
系统信息
/dev/cec设备已解决:
The device /dev/cec is settled:
q8723bs:/ # ls -l /dev/cec
crw-rw-rw- 1 root root 218, 0 2017-12-19 16:33 /dev/cec
我也可以在/sys/class/cec上找到它:
I also can find it on /sys/class/cec:
q8723bs:/ # ls -laht /sys/class/cec/
total 0
-r--r--r-- 1 root root 4.0K 2017-12-19 16:45 arc_port
lrwxrwxrwx 1 root root 0 2017-12-19 16:45 cec -> ../../devices/aocec/cec
-r--r--r-- 1 root root 4.0K 2017-12-19 16:45 cec_version
--w------- 1 root root 4.0K 2017-12-19 16:45 cmd
-rw-rw-r-- 1 root root 4.0K 2017-12-19 16:45 dbg_en
-rw-rw-r-- 1 root root 4.0K 2017-12-19 16:45 device_type
-r--r--r-- 1 root root 4.0K 2017-12-19 16:45 dump_reg
-rw-rw-r-- 1 root root 4.0K 2017-12-19 16:45 fun_cfg
-rw-rw-r-- 1 root root 4.0K 2017-12-19 16:45 menu_language
-r--r--r-- 1 root root 4.0K 2017-12-19 16:45 osd_name
-rw-rw-r-- 1 root root 4.0K 2017-12-19 16:45 physical_addr
-r--r--r-- 1 root root 4.0K 2017-12-19 16:45 pin_status
-r--r--r-- 1 root root 4.0K 2017-12-19 16:45 port_num
-rw-rw-r-- 1 root root 4.0K 2017-12-19 16:45 port_seq
-r--r--r-- 1 root root 4.0K 2017-12-19 16:45 port_status
-rw-rw-r-- 1 root root 4.0K 2017-12-19 16:45 vendor_id
-r--r--r-- 1 root root 4.0K 2017-12-19 16:45 wake_up
但是当我运行cec-client时,我会收到以下答案:
But when I ran cec-client I receive this answer:
q8723bs:/ # id
uid=0(root) gid=0(root) groups=0(root) context=u:r:toolbox:s0
q8723bs:/ # cec-client -s /dev/cec
opening a connection to the CEC adapter...
DEBUG: [ 1] Broadcast (F): osd name set to 'Broadcast'
DEBUG: [ 2] connection opened, clearing any previous input and waiting for active transmissions to end before starting
DEBUG: [ 396] communication thread started
DEBUG: [ 1396] command 'PING' was not acked by the controller
请注意,我还有一个设备/dev/input/event2,它是只读的cec_input:
As a note, I also have the device /dev/input/event2 that is a read only cec_input:
q8723bs:/ # ls -l /dev/input/event2
crw-rw---- 1 root input 13, 66 2017-12-19 16:33 /dev/input/event2
q8723bs:/ # ls /sys/devices/virtual/input/input2/
capabilities/ event2/ id/ modalias name phys power/ properties subsystem/ uevent uniq
q8723bs:/ # cat /sys/devices/virtual/input/input2/name
cec_input
我试图在/dev/input/event2上运行它,但是显然无法正常工作,因为它无法打开连接:
I tried to run it on /dev/input/event2 but obviously it didn't work because it could not open a connection:
q8723bs:/ # cec-client /dev/input/event2
No device type given. Using 'recording device'
CEC Parser created - libCEC version 4.0.2
opening a connection to the CEC adapter...
DEBUG: [ 1] Broadcast (F): osd name set to 'Broadcast'
ERROR: [ 3335] error opening serial port '/dev/input/event2': Couldn't lock the serial port
ERROR: [ 3335] could not open a connection (try 1)
摘要中:
在任何一种情况下,我都无法获得打开或更改电视输入源的命令.任何方向都将非常有帮助.预先感谢.
I could not get the command to turn on or change the input source of tv working in either of the cases. Any direction would be very helpful. Thanks in advance.
注意:我能够在同一台电视上使用libcec和raspberry pi来完成
note: I was able to accomplish it with libcec and raspberry pi on the same tv
推荐答案
因此,在解决此问题后,我发现,为了在android中启用CEC控件,您需要在shell上运行以下命令:/p>
So after a lot of work around this issue I figured out that, in order to have CEC control enabled in android you need to run this command on shell:
settings put global hdmi_control_enabled 1
#if you want, you can also enable this self-explanatory command
settings put global hdmi_control_auto_wakeup_enabled 1
#and this
settings put global hdmi_control_auto_device_off_enabled 1
此后,android会自动开始使用cec,例如在启动后,它会更改电视的输入源和/或打开电视.
After this, android automatically started to make use of cec, after a boot, for example, it change the input source of tv and/or turn on the tv.
现在,关于开发者控制:
我知道,当我调用该方法时:
I understood, that when I call the method:
Method m2 = obj_HdmiControlManager.getClass().getMethod("getPlaybackClient");
我基本上可以访问加密狗本身(而不是电视)的CEC.
I'm basically getting the access to CEC of the dongle itself (not the tv).
尽管如此,当我运行该方法时,我仍然继续收到null:
Though, I still continue to receive null when I run the method:
Method method_getTvClient = obj_HdmiControlManager.getClass().getMethod("getTvClient");
我的猜测是这是正常现象,因为加密狗本身是播放类型,而不是 TV 类型.
My guess here is that this is a normal behaviour since the dongle itself is a playback type and not a TV type.
因此,我尝试使用sendVendorCommand函数,但无法弄清楚如何使用它.我找不到有关此主题的任何文档/示例可以对我有所帮助.
So I tried to use the function sendVendorCommand but I was not able to figure out how to use it. I couldn't found any documentation/examples around this subject that could help me.
因此,我决定直接通过OS级别,它可以正常工作.特别是在此加密狗中,您在/sys/class/cec中有两个重要文件:
So I decided to go directly through OS level and it worked. Specifically in this dongle, you have at /sys/class/cec two important files:
.cmd(发送cec命令)
. cmd (to send cec commands)
例如(作为root @ android shell)
e.g. (as root @ android shell )
#turn on tv
echo 0x40 0x04 > /sys/class/cec/cmd
#change input source to HDMI 1
echo 0x4F 0x82 0x10 0x00 > /sys/class/cec/cmd
.dump_reg(读取cec的输出)
. dump_reg ( to read output of cec)
使用此网站来检查其他命令的代码
Use this site to check codes for other commands
就是这样!我更希望通过android框架执行这些命令,但至少可以这样做.
And That's it! I would prefer to execute those commands through android framework, but at least, this works.
这篇关于Android上的HDMI CEC的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!