whi*_*rue 5 android dongle hdmi-cec
我一直面临着在这个安卓加密狗上访问HDMI CEC的问题.
我正试图打开电视并改变电视的输入源,但我无法做到.
Android API方法
我正在运行一个系统应用程序,我已经解决了
<uses-permission android:name="android.permission.HDMI_CEC" />
Run Code Online (Sandbox Code Playgroud)
在AndroidManifest.xml上.
我通过反射访问HDMI服务,因为我无法直接访问它,即使是系统应用程序.
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);
}
}
Run Code Online (Sandbox Code Playgroud)
记录答案:
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
Run Code Online (Sandbox Code Playgroud)
我收到了真实:这意味着电视是真实的.如果电视关闭我收到假.这似乎有效.
虽然,每次我改变电视状态时,我都希望收到一个回调,但这并没有发生.任何的想法?
继续使用OneTouchPlayCallback的日志:
D/HdmiHelper: Start method onComplete | class $Proxy1 | interface android.hardware.hdmi.HdmiPlaybackClient$OneTouchPlayCallback
D/HdmiHelper: onComplete: 2
Run Code Online (Sandbox Code Playgroud)
查看类HdmiPlaybackClient.java如果一切顺利,答案将为0(@param结果是操作的结果.{@link HdmiControlManager#RESULT_SUCCESS.你可以在HdmiControlManager.java类中找到这个变量}.相反,我收到2我认为这是RESULT_SOURCE_NOT_AVAILABLE.
知道为什么吗?
继续使用DisplayStatusCallback的日志:
D/HdmiHelper: Start method onComplete | class $Proxy3 | interface android.hardware.hdmi.HdmiPlaybackClient$DisplayStatusCallback
D/HdmiHelper: onComplete: 2
Run Code Online (Sandbox Code Playgroud)
根据此回调的定义:
/**
* 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);
}
Run Code Online (Sandbox Code Playgroud)
并查看HdmiControlManager我收到的2意味着:
public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
Run Code Online (Sandbox Code Playgroud)
这是一个奇怪的结果,因为这不是正在发生的事情.
回答:
getActiveSource为null
我还测试了调用getTvClient()方法的代码:
Method method_getTvClient = obj_HdmiControlManager.getClass().getMethod("getTvClient");
Object obj_HdmiTvClient = method_getTvClient.invoke( obj_HdmiControlManager );
Log.d("HdmiHelper", "obj_HdmiTvClient: " + obj_HdmiTvClient);
Run Code Online (Sandbox Code Playgroud)
结果为null.
我也尝试过在CEC-O-MATIC网站之后发送供应商命令的方法,但我无法取得成功.如果您对此有任何指示,请给我一些指示,我会测试一下.
LibCEC方法:
由于这篇文章,我能够将libcec交叉编译到android .但是libcec总是回答我"命令'PING'没有受到控制器的攻击".
我已将标志-DHAVE_EXYNOS_API = 1和-DHAVE_AOCEC_API = 1添加到libcec.
系统信息
设备/ dev/cec已结算:
q8723bs:/ # ls -l /dev/cec
crw-rw-rw- 1 root root 218, 0 2017-12-19 16:33 /dev/cec
Run Code Online (Sandbox Code Playgroud)
我也可以在/ 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
Run Code Online (Sandbox Code Playgroud)
但当我运行cec-client时,我收到了这个答案:
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
Run Code Online (Sandbox Code Playgroud)
作为一个注释,我还有设备/ dev/input/event2是一个只读的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
Run Code Online (Sandbox Code Playgroud)
我试图在/ dev/input/event2上运行它,但显然它不起作用,因为它无法打开连接:
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)
Run Code Online (Sandbox Code Playgroud)
综上所述:
在任何一种情况下,我都无法打开或更改电视输入源的命令.任何方向都会非常有帮助.提前致谢.
注意:我能够在同一台电视上用libcec和raspberry pi完成它
因此,在解决此问题的大量工作之后,我发现,为了在 android 中启用 CEC 控制,您需要在 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
Run Code Online (Sandbox Code Playgroud)
此后,android 自动开始使用 cec,例如,在启动后,它会更改电视的输入源和/或打开电视。
现在,关于开发者控制:
我明白,当我调用该方法时:
Method m2 = obj_HdmiControlManager.getClass().getMethod("getPlaybackClient");
Run Code Online (Sandbox Code Playgroud)
我基本上可以访问加密狗本身(而不是电视)的 CEC。
但是,当我运行该方法时,我仍然继续收到 null:
Method method_getTvClient = obj_HdmiControlManager.getClass().getMethod("getTvClient");
Run Code Online (Sandbox Code Playgroud)
我的猜测是,这是一种正常行为,因为加密狗本身是一种播放类型,而不是电视类型。
所以我尝试使用函数 sendVendorCommand 但我无法弄清楚如何使用它。我找不到任何可以帮助我的有关此主题的文档/示例。
所以我决定直接通过操作系统级别并且它起作用了。特别是在此加密狗中,您在 /sys/class/cec 中有两个重要文件:
. cmd(发送cec命令)
例如(作为 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
Run Code Online (Sandbox Code Playgroud)
. dump_reg(读取cec的输出)
使用此站点检查其他命令的代码
就是这样!我更愿意通过 android 框架执行这些命令,但至少,这是有效的。
| 归档时间: |
|
| 查看次数: |
3210 次 |
| 最近记录: |