WebRTC Practice

最近着手了几个AR项目,使用了相当多的WebRTC功能。本文在此统一总结所有WebRTC遇到的锅和问题,以及(可能)的一些解决方案。

图片上传大小,朝向以及质量

Reference: http://jartto.wang/2017/11/28/h5-user-media/

现在的手机摄像头动不动就2000w,上传流量相对较大。

对于图像大小,这边给出两种方案:第一种是通过WebRTC的constraints来限定图片大小


    const constraints = {
      video: true,
      optional: [
        { minWidth: 320 },
        { minWidth: 640 },
      ]
    };

第二种是通过Canvas后期处理,Resize成对应的大小:


const canvas = document.createElement('canvas')

const ctx = canvas.getContext('2d')

ctx.drawImage(video, 0, 0, 640, 480) // expected height and width

const dataURI = canvas.toDataURL('image/jpeg');

对于图片朝向,参见图片朝向的另一篇文章。总体思路是通过图片的EXIF获取对应的信息之后,用Canvas转换过来。

对于图片质量,toDataURL第二个参数就是一个float,设定从0.0到1.0


canvas.toDataURL(type,quality);

前置、后置摄像头的切换


var front = false;
document.getElementById('flip-button').onclick = function() { front = !front; };
var constraints = { video: { facingMode: (front? "user" : "environment") } };

图片的实时化AR展示

由于我们的模型比较大,是一个ResNet的模型,基本就不可能在设备上跑。这边的思路是通过一个Python服务器去获取得到的Bounding Box等等信息,然后在前端对应展示出来。

一般来说,我们会把Video隐藏(因为这个是实时视频流,网络Latency后基本都对不上)然后把Post之前作为一个图层(BackCanvas),我们绘制的信息作为一个图层(FrontCanvas)然后用双绝对定位的方式,定位到一个位置上。

注意的是,我们获取摄像头信息,也需要一个Canvas。这个Canvas是“最新一帧”的内容,但是我们获取回来的结果,往往是“上一帧”的内容。所以这个Canvas我们也进行隐藏。然后,我们把这个Canvas的内容缓存成base64。在输出的时候,我们用Image读出来并且放到BackCanvas上面。

Image对象有个坑,就是最好先设定好onLoad事件再去赋值src,否则可能在赋值src的时候图片就已经加载完毕,导致onLoad不执行。

外部图片的附加展示(如道具效果)

这边的道具效果有两种思路。第一种是<img>对象加上 CSS Transformation的动画效果。这种效果的实时性比较差,但是可以很方便的使用CSS的各种动画。第二种是Canvas绘制加上rAF的动画效果。这里的rAF带来了额外的编程压力,而且性能也相对差点(rAF基本会堵塞整个渲染线程——如果一定要使用,请保证Handler能在16.67ms内完成所有工作)。解决这个问题的唯一方法是用WebWorker拉起多线程,并且用OffscreenCanvas渲染内容。这里的局限性在于兼容性,Chrome 69之前是Flagged的,基本上没有人会开启。

道具效果主要是一个Transform,一个是加载。

Transform是指将道具转换到图片的合适位置,缩放到合适大小。这里直接贴Snippet:


        const sticker = this.$refs.sticker;
        // console.log(sticker)
        const sWidth = sticker.naturalWidth;
        const sHeight = sticker.naturalHeight;
        const scaler = item.box[2] / sWidth;
        ctx.drawImage(sticker, 0, 0, sWidth, sHeight, item.box[0], item.box[1],
          sWidth * scaler,
          sHeight * scaler)

加载的时候,需要注意图片如果跨域,可能会遭到阻止(污染Canvas)。

这里我们需要给图片加上crossorigin属性,让图片也带Option请求。


<img crossorigin="anonymous" style="display: none" ref="sticker" :src="stickerSrc">

如果是动态创建的(new Image()),同样是设置image.crossOrigin = 'Anonymous'

限流

没什么技术含量。这边的思路是上传即占满带宽,所以直接每次只有一个上传即可。

然后通过尾递归的方式,反复调用capture方法。

兼容性

最后说说兼容性。WebRTC在移动端基本全挂,测试MIUI,华为浏览器,Via浏览器等一众Webview/Blink浏览器都直接拒绝。拒绝的特征为video标签是黑色,canvas绘制为透明,getUserMedia抛出异常。

尝试过gumwrapper等一众的shim,毫无作用。

所以如果真的要考虑兼容性,建议还是给用户端带上Chrome浏览器的安装包。Chrome浏览器的实现非常稳定,没有出现过什么问题,除了又一次由于系统原因闪退以外。这里注意Chrome浏览器的内存管理比较极端,如果用户内存不足,即使是Capture类型的input,都会因为内存问题而终止。

最后提一下微信、QQ、X5浏览器,这个浏览器是支持WebRTC的,已知的问题是只能调用前置摄像头,而且没有切换功能,对于分辨率的调整也是坏的。所以还是算了吧。

桌面端的话,直接shipped with chrome就好。实在不行还有Electron!