miracast uibc详解

在这里插入图片描述
如上图所示,官方wifi display参考架构里有一个uibc的功能一直没用上。目前已知的信息是,这个是用来做反向控制的一个功能,在电视上即可对手机进行控制。我们现在就来研究一下

1. UIBC定义

User Input Back Channel
用户输入反向控制通道(UIBC)是一个可选的WFD特性,实现该扩展功能,有助于用户从WFD sink端控制WFD source端的通信。
wifi display spec文档定义:

4.11 User Input Back Channel The User Input Back Channel (UIBC) is an optional WFD feature that when implemented facilitates communication
of user inputs to a User Interface, present at the WFD Sink, to the
WFD Source. All UIBC user inputs are packetized using a common packet
header and transported over TCP/IP. The user input categories include
Generic, and HIDC. The Generic category is used for device agnostic
user inputs that are processed at the application level. Generic user
inputs are formatted using the Generic Input Body. HIDC is used for
user inputs generated by HIDs like remote control, keyboard, etc.[46].
HIDC user inputs are formatted using the HIDC Input Body. 翻译一下
4.11 UIBC UIBC是WFD的可选feature,便于用户在UI上用户交流?展现在sink端,到source端?什么鬼! 所有uibc用户输入被打包成common的包头,然后用tcp/ip进行传输,user input类别包括HIDC和Generic。
Generic类别被用于在应用程序级别处理的与设备无关的用户输入。 HIDC被用于通过HIDs(一类HID类设备)生成的user
input,像远程控制,键盘等等,UIDC user inputs被用UIDC Input Body来格式化

虽然翻译的乱七八糟,但还是大概有了点概念。
接下来是

1.1 UIBC的TCP/IP封包形式:

在这里插入图片描述
The fields in the common packet header are described below:

  • Version (3 bits):
    The version of the protocol. This field shall be set to 0b000.
  • T (1 bit):
    Presence of the optional timestamp before the input body, where 0 means the timestamp field does not exist, and 1 means the timestamp field exists.猜测这是一个事件对应的时间戳,可以设置0.时间就不设置了
  • Reserved (8 bits):
    预留了8bits
  • Length (16 bits):
    整个 TCP 有效负载的长度,以 8 位为单位,从位偏移量 0 到 UIBC 输入正文的结尾
    (包括填充,如果有的话)。
  • Input Category (4 bits):
    就是前面提到的Generic或者HIDC,前者用0,后者用1.用来表明当前这条UIBC的类型是哪个。
  • UIBC Input Body:
    此字段是Generic输入正文或 HIDC 输入正文,如输入类别字段中所示,包含
    描述一个或多个用户输入的信息。 一个用户输入对应一个Generic输入消息或一个 HIDC 消息,具体
    取决于所选的输入类别。 此外,该字段应填充为整数16 位的倍数,以便在 Length 字段中具有偶数。

2. UIBC的建立和维护

UIBC是使用RTSP GET_PARAMETER 和SET_PARAMETER消息建立和维护的。
消息序列如下两个Figure
在这里插入图片描述
在这里插入图片描述
WFD Source用于 UIBC 消息事务的 TCP 端口号包含在 wfd-uibc-capability 中RTSP M4
和/或 M14 请求消息中的参数,并且 WFD Source上的该端口应准备好接受在发送后续
RTSP M4 和/或 M14 请求消息之前从 WFD Sink传入的连接包含 wfd-uibc-capability 参数。
一旦建立,WFD Source和 WFD Sink之间的单个 TCP 连接WFD Sink应在 WFD 会话期间
用于所有 UIBC 数据交换。

2.1 UIBC Input Body

2.1.1 Generic input Body Format:

在这里插入图片描述
类型那里,有鼠标/Touch按下抬起,鼠标移动,放大,滚动,旋转等等,具体参考
《Wi-Fi_Display_Technical_Specification_v2.1_0》表15即可。第三列的Describe这里基本就是显示区域的坐标了。

2.1.2 HIDC Input Body Format

在这里插入图片描述
HIDC Input Path如表23所示,有红外线,USB,蓝牙,Zigbee,Wi-Fi等
HID Type有键盘鼠标游戏手柄,相机,手势,远程控制等

3. source端实例分析

github中 hw手机fw代码uibc部分研究
代码如下:反编译出来的代码
https://github.com/SivanLiu/HwFrameWorkSource/blob/5b92ed0f1ccb4bafc0fdb08b6fc4d98447b754ad/Mate20_9_0_0/src/main/java/com/android/server/display/HwUibcReceiver.java(疑似华为手机source uibc的实现)

3.1 创建UIBC接收器

WifiDisplayController.this.mUibcInterface.createReceiver,调用的是HwUibcReceiver中的createReceiver方法
在这里插入图片描述
由于这个HwUibcReceiver继承自HandlerThread,所以这里调用的start方法会启动线程.线程里做什么还没找到。
然后调用了CreateHandler方法,初始化了mHandler为UibcHandler
在这里插入图片描述
msg为1的时候,干的事情大概是初始化的,log可以看出是UIBC_START了。并且在后面有调用
Socket unused2 = HwUibcReceiver.this.mSocket = HwUibcReceiver.this.mServer.accept();
做一个等待连接
socket的初始化在前面HwUibcReceiver构造函数中:
在这里插入图片描述
有连接过来之后使用这条socket初始化了mInput
BufferedInputStream unused4 = HwUibcReceiver.this.mInput = new BufferedInputStream(HwUibcReceiver.this.mSocket.getInputStream());
然后便是
HwUibcReceiver.this.receiveEvent();
在这里插入图片描述
发了msg what为2.即是
在这里插入图片描述
主要就是read从socket收到的数据,然后consumePacket进行消费,然后接着调用receiveEvent。
大概流程清楚了。
那我们接着分析消费的流程。消费 = 解析 + 处理

3.2 消费socket包consumePacket

在这里插入图片描述
跟协议相对应,先看当前的input category是哪种,前面协议部分已经说过了,0是generic事件,1是hid事件。很遗憾,这里hid调用了native方法,而对应的native代码肯定是无法被反编译的。那这里就先不分析了。我们先研究Generic Input

3.3 处理Generic Input

在这里插入图片描述
对前文提到的Generic类型的input有所呼应。这个函数大概做的事情

3.4 解析输入正文resolveInputBody

在这里插入图片描述
payload的第一位表明了inputType.协议table15中有定义,按下抬起,滚动,缩放等。
后面2位计算出bodyLength之后向前移动三字节。最后面那个mParseBytes应该是全局记录当前解析的位置的变量。

3.5 解析事件resolveEvent

在这里插入图片描述
根据inputType分别调用createKeyEvent或者createTouchEvent来创建不同的InputEvent对象。
我们先看简单的

3.5.1 创建key事件createKeyEvent

在这里插入图片描述
请注意这里new的KeyEvent对象其实继承自InputEvent.
这里调用的构造函数是这个:
在这里插入图片描述
按下时间点,事件发生时间点,action这个大家都很熟悉了,code也是,调用者传入的
b = payload[index + 5];说明偏移5个字节(这里为什么偏移5?)指示的就是事件的code,即这些:
在这里插入图片描述

3.5.2 创建touch事件createTouchEvents

在这里插入图片描述
遍历payload,然后调用doTranslateCoord翻译坐标,虽然这里传入参数和解析过程应该都很重要,但是看着比较复杂。简单来说就是根据sink传过来的左边做了一个坐标点的转换。

3.6 注入input事件injectInputEvent

遍历所有的Event,然后分别调用InputManager的injectInputEvent接口,注入事件
在这里插入图片描述
也就是说构造一个InputEvent对象送进入,只要这个event合理合法,系统就会作出响应。

4. sink端实例分析(控制台版本)

代码地址:
https://github.com/albfan/miraclecast/tree/master/src/uibc
看上去只是一个控制台的二进制实现,并不是电视端的,但是大致原理应该是相通的
在这里插入图片描述

4.1 socket连接

在这里插入图片描述
输入两个参数,参数1是ip地址,参数2是端口
在这里插入图片描述
创建一路TCP连接,跟前面source端对应起来理解
UIBC这种case里,被反向控制的设备是server,而大屏端其实是client

  if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
   
    perror("ERROR connecting");
    return EXIT_FAILURE;
  }

然后接收控制台输入事件

	bzero(buffer, 256);
    fgets(buffer, 255, stdin);
    char type = buffer[0];

    UibcMessage uibcmessage;
    // 0 1 3 4定义在spec里 table 15
    if (type == '0' || type == '1') {
   
      uibcmessage = buildUibcMessage(GENERIC_TOUCH_DOWN, buffer, 1, 1);
    } else if (type == '3' || type == '4') {
   
      uibcmessage = buildUibcMessage(GENERIC_KEY_DOWN, buffer, 1, 1);
    } else {
   
      if (!daemon) {
   
        printf("unknow event type: %s", buffer);
      }
      continue;
    }

4.2 打包Input Body

组包过程如下
// UibcMessage结构体定义
typedef struct {
   
   char* m_PacketData;
   size_t m_PacketDataLen;
   bool m_DataValid;
} UibcMessage;

UibcMessage buildUibcMessage(MessageType type,
    const char* inEventDesc,
    double widthRatio,
    double heightRatio) {
   
  UibcMessage uibcmessage;
  uibcmessage.m_PacketData = NULL;
  uibcmessage.m_PacketDataLen = 0;
  uibcmessage.m_DataValid = false;

  switch (type) {
   
    case GENERIC_TOUCH_DOWN:
    case GENERIC_TOUCH_UP:
    case GENERIC_TOUCH_MOVE:
      getUIBCGenericTouchPacket(inEventDesc,
          &uibcmessage,
          widthRatio,
          heightRatio);
      break;

    case GENERIC_KEY_DOWN:
    case GENERIC_KEY_UP:
      getUIBCGenericKeyPacket(inEventDesc, &uibcmessage);
      break;

    case GENERIC_ZOOM:
      getUIBCGenericZoomPacket(inEventDesc, &uibcmessage);
      break<
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个简单的Android代码示例,演示如何在Miracast连接中使用Miracast UIBC传输用户输入: 首先,在AndroidManifest.xml文件中添加以下权限和特性: ```xml <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-feature android:name="android.hardware.wifi.direct" android:required="true" /> ``` 然后,在你的Activity中添加以下代码: ```java import android.content.Context; import android.media.MediaRouter; import android.view.InputDevice; import android.view.InputEvent; import android.view.KeyEvent; import android.view.MotionEvent; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private MediaRouter mMediaRouter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMediaRouter = (MediaRouter) getSystemService(Context.MEDIA_ROUTER_SERVICE); } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { sendUIBCKeyEvent(event); } return super.dispatchKeyEvent(event); } @Override public boolean dispatchGenericMotionEvent(MotionEvent event) { sendUIBCMotionEvent(event); return super.dispatchGenericMotionEvent(event); } private void sendUIBCKeyEvent(KeyEvent event) { MediaRouter.RouteInfo selectedRoute = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO); if (selectedRoute != null && selectedRoute.isEnabled()) { InputEvent inputEvent = new KeyEvent(event.getDownTime(), event.getEventTime(), event.getAction(), event.getKeyCode(), event.getRepeatCount(), event.getMetaState(), event.getDeviceId(), event.getScanCode(), event.getFlags(), event.getSource()); selectedRoute.sendControlEvent(MediaRouter.ControlRequestCallback.NONE, inputEvent); } } private void sendUIBCMotionEvent(MotionEvent event) { MediaRouter.RouteInfo selectedRoute = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO); if (selectedRoute != null && selectedRoute.isEnabled()) { InputEvent inputEvent = MotionEvent.obtain(event); selectedRoute.sendControlEvent(MediaRouter.ControlRequestCallback.NONE, inputEvent); } } } ``` 这段代码中,我们首先在onCreate方法中获取MediaRouter实例。然后,在dispatchKeyEvent方法中检测按键事件,将其封装成UIBC的KeyEvent并通过MediaRouter发送到显示设备。同样地,在dispatchGenericMotionEvent方法中检测触摸事件,将其封装成UIBC的MotionEvent并发送到显示设备。 请注意,这只是一个简单的示例,实际使用时还需要根据你的具体需求进行适配和错误处理。 希望这个示例能帮到你!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值