libkosapi api

libkosapi是个跨平台库,目前实现上已有android、windows。主要功能是支持app实现远程桌面。

一、录屏

在如何处理录屏上,libkosapi假设了定么个运行模型:录屏操作专门工作在一个新线程,称为录屏线程。一旦远程桌面(rdp)服务器收到来自rdp客户端的连接请求,app用类似以下的操作处理录屏。步骤中rdpd线程是服务器端处理rdp服务的主线程,当然,app按自个需要也可以是其它线程。

  1. (rdpd线程)调用kosGetDisplayInfo获得当前屏幕信息。当中会返回一个指示当前朝向的字段,称为初始朝向。
  2. (rdpd线程)创建录屏线程。
  3. (录屏线程)调用kosRecordScreenLoop,传下一个类型是fdid_gui2_screen_captured的函数指针,指向app自写的一个函数did_gui2_screen_captured。
  4. (录屏线程)libkosapi采到一帧后,调用app中的采帧回调did_gui2_screen_captured。
  5. (录屏线程)app自写的采帧回调did_gui2_screen_captured,执行着采到一帧后,怎么处理。参数pixel_buf、length分别指示了该帧内存地址、字节数。width、height是该帧宽度、高度。flags是和该帧绑定的标记,像朝向,这个朝向称为帧朝向。user是app调用kosRecordScreenLoop时,和did一起传来的参数。对如何处理,app很快会把帧数据发向其它线程,要注意作好多线程同步。录屏过程中可能发生朝向改变,app应该用这里的帧朝向结合初始朝向,更多处理朝向参考“RDP(5/5):Leagor Virtual Channel Extension”中的“一、运行时改变朝向”。
  6. (rdpd线程)发现应该暂停录屏线程,像rdp客户端app被最小化,暂时不需要显示远程图像了,则调用kosPauseRecordScreen(true)。要从暂停恢复采集,则调用kosPauseRecordScreen(false)。kosRecordScreenPaused可用于判断当前是否处于暂停状态。
  7. (rdpd线程)不须要录屏了,像rdp客户端断开了,依次调用1)kosStopRecordScreen结束录屏,2)销毁录屏线程。

1.1 kosGetDisplayInfo

获得屏幕当前信息。通常在开始录屏前调用。

void kosGetDisplayInfo(KosDisplayInfo* info);

参数

typedef struct {
    uint32_t w;
    uint32_t h;
    float xdpi;
    float ydpi;
    float fps;
    float density;
    uint8_t orientation;
    bool secure;
} KosDisplayInfo;
  • w:屏幕在0度朝向时的宽度。示例:1920。
  • h:屏幕在0度朝向时的页面度。示例:1080。
  • orientation:屏幕当前朝向。KOS_DISPLAY_ORIENTATION_0、KOS_DISPLAY_ORIENTATION_90、KOS_DISPLAY_ORIENTATION_180、KOS_DISPLAY_ORIENTATION_270中一个值。
  • fps:显示刷新率。示例:60。
  • xdpi:对应Java中DisplayMetrics.xdpi。示例:81.28、320。
  • ydpi:对应Java中DisplayMetrics.ydpi。示例:80.682、320。
  • density、secure。可选填项,当前没有使用。

1.2 kosRecordScreenLoop

启动并一直录屏。这是个阻塞式操作,要函数退出,除非调用了结束录屏(kosStopRecordScreen)或发生错误。

typedef void (*fdid_gui2_screen_captured)(uint8_t* pixel_buf, int length, int width, int height, uint32_t flags, void* user);
int kosRecordScreenLoop(uint32_t bitrate_kbps, uint32_t max_fps_to_encoder, uint8_t* pixel_buf, fdid_gui2_screen_captured did, void* user);
  • bitrate_kbps。指示放入内存是h264数据还是未压缩的帧。0表示未原压缩的帧。其它值表示要压缩到的码率,单位是kb,举个例子,8000表示8M h264。
  • max_fps_to_encoder。要送入视频编码器的最大帧率。必须设置,这个值一般设为25。
  • pixe_buf。libkosapi采到一帧后,帧数据要放到的地址,由app负责创建、销毁。因为它要能存储一帧最大可能的字节数,创建时分配的字节数建议用“w * h * 4”,w、h分别是屏幕宽度、高度,4表示有一个像素是4个字节。传给didScreenCaptured的参数pixel_buf,值就是它。
  • did。一个函数指针,指向app提供的、如何处理一新帧的函数did_gui2_screen_captured。libkosapi采到一帧后,会调用它。
  • user。app自定义的一个参数。libkosapi在调用did时,会把它原封不动传下来。

kosRecordScreenLoop代码抄自<aosp>/frameworks/av/cmds/screenrecord,更多录屏信息参考“SurfaceFlinger(1/3) 和app直接通讯api”中“2.1 录屏”。kosRecordScreenLoop的bitrate_kbps指示放入内存是h264数据还是未压缩的帧。实测下来,必须使用第一种方式,即surfaceflinger生成图像放在gpu,gpu就地送给mediacodec执行h264压缩,放入内存的已是h264数据。要是采用第二种,图像先放内存再送mediacodec压缩,即使是rk3399这种能力算强的cpu,也是会卡。为此Launcher传的bitrate_kbps不会是0,目前是8000,表示8M h264。

1.3 kosStopRecordScreen

结束录屏。禁止在录屏线程调用。

void kosStopRecordScreen()

 

1.4 kosPauseRecordScreen

暂停录屏。禁止在录屏线程调用。

void kosPauseRecordScreen(bool pause);
  • pause。true:暂停录屏,false:恢复录屏。

暂停不是退出kosRecordScreenLoop,但至少应做到不会调用app自写的采帧函数did_gui2_screen_captured。

为有个直观理解,简单说下android是如何实现暂停。它就是向MediaCodec模块发“drop-input-frames = true”的消息,MediaCodec收到这消息后,虽然会继继运行,但不对新帧进行编码,也就不会后绪调用app设置的did_gui2_screen_captured了。一旦要恢复录屏,方法就是改发“drop-input-frames = false”。

1.5 kosRecordScreenPaused

判断当前是否处于暂停录状态。

bool kosRecordScreenPaused()

 

二、模拟输入

输入包括鼠标、键盘输入,在android,模拟输入技术用的是/dev/uinput,uinput创建的新设备放在/dev/input,这样在inputflinger看来,这设备已和其它输入设备没什么两样。这技术是linux实现。

为什么要提供模拟输入api?——在远程桌面应用中,最先是由rdp client产生输入事件,然后通过rdp协议,事件传向了rdp server。rdp server收到事件时,事件采用的编码是rdp协议指定的windows扫描码,不是server设备本身用的编码。举个例子,对键盘按键“1”,windows扫描码是0x2,android操作系统的键盘码则是0x8。这时就要转码,然后再把转码后的事件发向本地鼠标、键盘。模拟输入api就执行着转码、发送这两个任务。

在如何处理模拟输入上,libkosapi建议了定么个操作步骤。

  1. (录屏线程)调用kosRecordScreenLoop前,调用kosCreateInput,打开/dev/uinput设备文件fd。
  2. (rdpd线程)有待处理的鼠标、键盘事件了,调用kosSendInput,让转码并发向本机鼠标、键盘设备。
  3. (录屏线程)kosRecordScreenLoop退出后,调用kosDestroyInput,关闭/dev/uinput设备文件fd。

2.1 kosCreateInput

打开/dev/uinput设备文件fd。

bool kosCreateInput(bool keyboard, int screen_width, int screen_height);
  • keyboard。是否要显示软键盘。当rdp client是pc时,像windows,此时输字母不须要弹出软键盘,keyboard置为false。rdp client是移动端,像ios、android,keyboard置为true,表示须要弹出软键盘。
  • screen_width。0度朝向时的屏幕宽度。示例:1920。
  • screen_height。0度朝向时的屏幕高度。示例:1080。

2.2 kosDestroyInput

关闭/dev/uinput设备文件fd。

void kosDestroyInput();

2.3 kosSendInput

向设备文件fd发键盘/鼠标操作,这些操作会转成指定操作系统的编码,并最终发向键盘/鼠标硬件。

uint32_t kosSendInput(uint32_t input_count, KosInput* inputs);
  • input_count。操作个数。
  • inputs。存储着具体是什么操作。一个KosInput对应一个操作。
typedef struct {
    int    dx;
    int    dy;
    uint32_t   mouse_data;
    uint32_t   flags;
    uint32_t   time;
    // ULONG_PTR  dwExtraInfo;
} KosMouseInput;

typedef struct {
    uint16_t   virtual_key;
    uint16_t   scan_code;
    uint32_t   flags;
    uint32_t   time;
    // ULONG_PTR dwExtraInfo;
} KosKeybdInput;

#define KOS_INPUT_MOUSE     0
#define KOS_INPUT_KEYBOARD  1
#define KOS_INPUT_HARDWARE  2

typedef struct {
    uint32_t   type;
    union
    {
        KosMouseInput      mi;
        KosKeybdInput      ki;
    } u;
} KosInput;

一个KosInput对应一个操作。type指示了操作类型,一个操作或是键盘操作(KOS_INPUT_KEYBOARD),或是鼠标操作(KOS_INPUT_MOUSE)。

KosKeybdInput存储着一键盘事件,字段scan_code是输入,编码格式是windows扫描码。在给出的libkosapi.so实现中,转码用着以下过程。括号中的是键盘按键“1”对应的数值。

windows扫描码(2)--> usb_code(30)--> android_scan_code(8)

中间引入了usb_code编码,它是usb.org使用一个键盘码表,充作一种能包含所有字符的码表。当然,在最后发向键盘时,android_scan_code还要转成linux_code。

全部评论: 0

    写评论: