tkorays|言剑 Write code every day...

谈谈桌面共享中的鼠标优化

tkorays: 未经同意,不得转载。

1. 背景

桌面共享是办公协作中的一个常见的应用场景,在windows和mac上共享桌面中会涉及到鼠标的移动。大部分视频会议厂商都是选择桌面鼠标一起传输,这样做比较简单,但是这个并不是一个好的方案。

为了更好的用户体验,目前已经有很多厂商选择了鼠标单独传输,比如zoom、钉钉等。这样做有很多好处:

  • 鼠标可以以更高的帧率传输,这样接收端看到的鼠标更加流畅,更接近共享段体验
  • 鼠标独立传输,在桌面静止的时候,视频帧率可以做到很低
  • 目前很多会议室里面有投屏设备,用户一般是边看投屏边操作,因此流畅的鼠标对投屏体验有很大提升
  • 在弱网场景,桌面共享效果很差的时候,鼠标可以依旧很流畅。

2. 实现

这里根据对各个厂商的研究,大概谈谈实现大家的可能的方案。

2.1 采集

鼠标单独传输的第一步是鼠标单独采集。windows和mac都有提供方法,在屏幕采集的时候可以选择不采集鼠标,同时也提供了只只采集鼠标位置的接口。

做得一般的厂商可能到这里就停了,但是优秀的厂商可不止步于此。他们不仅仅要鼠标的位置,还需要鼠标的形状。windows目前是提供了接口可以获取鼠标形状,大部分鼠标形状有系统的ID,在windows里面有响应的宏标识。对于无效ID的一般需要用像素表示(这里主要分成两种类型,黑白的和彩色的,其中黑白的鼠标由两部分组成,像素和mask,彩色的鼠标就是普通的RGBA格式。鼠标形状宽高不是固定的,宽有16、32、64甚至128,保存的时候需要注意。

还有个需要注意的鼠标的hotspot,在接收端也是需要知道的信息。鼠标的hotspot就是鼠标真实点击的点,相对于鼠标的bitmap的位置。windows的cursor信息见: Cursors - Win32 apps

鼠标采集帧率为多少比较合适?调一调就知道了,哈哈。windows可以采集到60fps。太高了接收端渲染可能会cpu高,太低了优化效果不明显。

2.2 传输

对于鼠标来说,有ID的鼠标类型直接传输鼠标的ID和位置信息,没有ID的传一个无效ID和位置信息,在传输位置前先传输鼠标的图像(鼠标图像很小的)。因为鼠标图像改变并不是很频繁,所以一般只需要在改变的时候传输。

小伙伴们肯定会问,网络中有丢包,该怎么办?一句话,抗它。简单的做法就是使用RFC2198的方式,每个包里面带上前几个鼠标位置信息,这样可以抗很大的随机丢包。如果不嫌麻烦可以用FEC去抗。

如果是鼠标图形丢失了呢?如果丢失了,在RTT比较小的时候可以使用NACK的方式,请求重传;如果RTT比较大,那丢了就丢了吧,影响不是很大。在图像重传过程中鼠标正常播放,这时候可以回退到默认鼠标渲染,等到收到鼠标图像的时候再渲染真实的鼠标图像。这里面可优化的点很多,就不细说了。

2.3 接收

网络存在抖动,接收到鼠标后是否要做平滑呢? 网络抖动不大的时候不建议做平滑,直接按照接收顺序播放效果也是相当可以的。 如果抖动大的时候,按照顺序播放,可能会出现丢帧,但是只要你的采集帧率够高,丢包不是问题。 这个方案主要问题在于,有抖动、延迟的时候,优化效果就不是很明显了。引入jitter buffer肯定会引入延迟,个人觉得需要根据网络状态判断是否需要开启jitter buffer。如果大量的鼠标帧因为迟到被丢弃,那么就需要考虑引入jitter buffer了。

2.4 渲染

对于有ID的鼠标,可以加载系统的鼠标绘制;无效ID的拿接收到的像素做绘制。 鼠标贴图主要难点在于反色之类的操作,我们平时移动鼠标的时候发现鼠标在白底上是黑色,在黑底上是白色,反色处理大家多google就好。黑白和彩色的鼠标处理不一样,黑白的主要是异或处理,贴一个MSDN的官方例子,可以参考: Using Cursors

关于RGBA的alpha通道的处理,可以见维基百科: Alpha Compositing

关于RGBA鼠标的alpha通道,这里需要通过遍历像素,查看其alpha通道是否有数据,才能确认是否需要处理alpha通道,这个可以见webrtc代码:

// Scans a 32bpp bitmap looking for any pixels with non-zero alpha component.
// Returns true if non-zero alpha is found. |stride| is expressed in pixels.
bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height) {
  const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data);
  for (int y = 0; y < height; ++y) {
    for (int x = 0; x < width; ++x) {
      if (plane->rgbReserved != 0)
        return true;
      plane += 1;
    }
    plane += stride - width;
  }

  return false;
}

mac平台的鼠标没有反色机制,处理起来会比较简单。windows平台上,还是按照反色处理比较好,不然会比较怪异。如果不这样做,加黑边或者白边可能是没有写好的错误做法。webrtc里面对鼠标处理也有描边的处理,大家也可以参考。 还有个难点是鼠标位置,在图像边缘绘制鼠标需要格外注意。以及鼠标hotspot点位置要对,最近看某钉的鼠标位置就有问题,可能是这块处理不对。

鼠标在不移动的时候不采集,疯狂移动是采集帧率会很高,渲染端渲染压力可能会比较大。可以针对绘制做一些优化,比如使用GPU绘制、部分刷新、SSE指令集优化等等。如果觉得帧率太高影响cpu,可以考虑下均匀丢帧。

3. 总结

上面瞎谈了下桌面共享中的鼠标优化,抛个砖。


如果您觉得文章对您有用能够解决您的问题,欢迎您通过扫码进行打赏支持,谢谢!