默认
打赏 发表评论 4
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
直播系统聊天技术(五):微信小游戏直播在Android端的跨进程渲染推流实践
微信扫一扫关注!

本文由微信开发团队工程师“virwu”分享,发布于WeMobileDev公众号。


1、引言


近期,微信小游戏支持了视频号一键开播,将微信升级到最新版本,打开腾讯系小游戏(如跳一跳、欢乐斗地主等),在右上角菜单就可以看到发起直播的按钮一键成为游戏主播了(如下图所示)。

1.png

然而微信小游戏出于性能和安全等一系列考虑,运行在一个独立的进程中,在该环境中不会初始化视频号直播相关的模块。这就意味着小游戏的音视频数据必须跨进程传输到主进程进行推流,给我们实现小游戏直播带来了一系列挑战。

2、系列文章


本文是系列文章中的第5篇:


3、视频采集推流


3.1录屏采集?


小游戏直播本质上就是把主播手机屏幕上的内容展示给观众,自然而然地我们可以想到采用系统的录屏接口MediaProjection进行视频数据的采集。

这种方案有这些优点:

  • 1)系统接口,实现简单,兼容性和稳定性有一定保证;
  • 2)后期可以扩展成通用的录屏直播;
  • 3)对游戏性能影响较小,经测试对帧率影响在10%以内;
  • 4)可以直接在主进程进行数据处理及推流,不用处理小游戏跨进程的问题

但是最终这个方案被否决了,主要出于以下考虑:

  • 1)需要展示系统授权弹窗;
  • 2)需要谨慎处理切出小游戏后暂停画面推流的情况,否则可能录制到主播的其他界面,有隐私风险;
  • 3)最关键的一点:产品设计上需要在小游戏上展示一个评论挂件(如下图),便于主播查看直播评论以及进行互动,录屏直播会让观众也看到这个组件,影响观看体验的同时会暴露一些只有主播才能看到的数据

2.png

3.png

转念一想,既然小游戏的渲染完全是由我们控制的,为了更好的直播体验,能否将小游戏渲染的内容跨进程传输到主进程来进行推流呢?

3.2小游戏渲染架构


为了更好地描述我们采用的方案,这里先简单介绍一下小游戏的渲染架构:

4.png

可以看到图中左半边表示在前台的小游戏进程,其中MagicBrush为小游戏渲染引擎,它接收来自于小游戏代码的渲染指令调用,将画面渲染到在屏的SurfaceView提供的Surface上。整个过程主进程在后台不参与。

3.3小游戏录屏时的情况


小游戏之前支持过游戏内容的录制,和直播原理上类似,都需要获取当前小游戏的画面内容。

录屏启用时小游戏会切换到如下的模式进行渲染:
5.png

可以看到,MagicBrush的输出目标不再是在屏的SurfaceView,而是Renderer产生的一个SurfaceTexture。

这里先介绍一下Renderer的作用:

Renderer是一个独立的渲染模块,表示一个独立的GL环境,它可以创建SurfaceTexture作为输入,收到SurfaceTexture的onFrameAvailable回调后通过updateTexImage方法将图像数据转换为类型是GL_TEXTURE_EXTERNAL_OES的纹理参与后续的渲染过程,并可以将渲染结果输出到另一个Surface上。


下面逐步对图中过程进行解释:

1)MagicBrush接收来自小游戏代码的渲染指令调用,将小游戏内容渲染到第一个Renderer所创建的SurfaceTexture上;

2)随后这个Renderer做了两件事情:

  • 2.1)将得到的小游戏画面纹理再次渲染到了在屏的Surface上;
  • 2.2)提供纹理ID给到第二个Renderer(这里两个Renderer通过共享GLContext来实现共享纹理)。

3)第二个Renderer将第一个Renderer提供的纹理渲染到mp4编码器提供的输入SurfaceTexture上,最终编码器编码产生mp4录屏文件。

3.4改造录屏方案?


可以看到,录屏方案中通过一个Renderer负责将游戏内容上屏,另一个Renderer将同样的纹理渲染到编码器上的方式实现了录制游戏内容,直播其实类似,是不是只要将编码器替换为直播的推流模块就可以了呢?

确实如此,但还缺少关键的一环:推流模块运行在主进程,我们需要实现跨进程传输图像数据!如何跨进程呢?

说到跨进程:可能我们脑海里蹦出的第一反应就是Binder、Socket、共享内存等等传统的IPC通信方法。但仔细一想,系统提供的SurfaceView是非常特殊的一个View组件,它不经过传统的View树来参与绘制,而是直接经由系统的SurfaceFlinger来合成到屏幕上,而SurfaceFlinger运行在系统进程上,我们绘制在SurfaceView所提供的Surface上的内容必然是可以跨进程进行传输的,而Surface跨进程的方法很简单——它本身就实现了Parcelable接口,这意味着我们可以用Binder直接跨进程传输Surface对象。

于是我们有了下面这个初步方案:
6.png

可以看到:第3步不再是渲染到mp4编码器上,而是渲染到主进程跨进程传输过来的Surface上,主进程的这个Surface是通过一个Renderer创建的SurfaceTexture包装而来的,现在小游戏进程作为生产者向这个Surface渲染画面。当一帧渲染完毕后,主进程的SurfaceTexture就会收到onFrameAvailable回调通知图像数据已经准备完毕,随之通过updateTexImage获取到对应的纹理数据,这里由于直播推流模块只支持GL_TEXTURE_2D类型的纹理,这里主进程Renderer会将GL_TEXTURE_EXTERNAL_OES转换为GL_TEXTURE_2D纹理后给到直播推流编码器,完成推流过程。

经过一番改造:上述方案成功地实现了将小游戏渲染在屏幕上的同时传递给主进程进行推流,但这真的是最优的方案吗?

思考一番,发现上述方案中的Renderer过多了,小游戏进程中存在两个,一个用于渲染上屏,一个用于渲染到跨进程而来的Surface上,主进程中还存在一个用于转换纹理以及调用推流模块。如果要同时支持录屏,还需要在小游戏进程再起一个Renderer用于渲染到mp4编码器,过多的Renderer意味着过多的额外渲染开销,会影响小游戏运行性能。

3.5跨进程渲染方案


纵观整个流程,其实只有主进程的Renderer是必要的,小游戏所使用的额外Render无非就是想同时满足渲染上屏和跨进程传输,让我们大开脑洞——既然Surface本身就不受进程的约束,那我们干脆把小游戏进程的在屏Surface传递到主进程进行渲染上屏吧!

7.png

最终我们大刀阔斧地砍掉了小游戏进程的两个冗余Renderer,MagicBrush直接渲染到了跨进程传递而来的Surface上,而主进程的Renderer在负责纹理类型转换的同时也负责将纹理渲染到跨进程传递而来的小游戏进程的在屏Surface上,实现画面的渲染上屏。

最终所需要的Renderer数量从原来的3个减少到了必要的1个,在架构更加清晰的同时提升了性能。

后续需要同时支持录屏时,只要稍作改动,将mp4编码器的输入SurfaceTexture也跨进程传递到主进程,再新增一个Renderer渲染纹理到它上面就行了(如下图所示)。

8.png

3.6兼容性与性能


到这里,不禁有点担心,跨进程传输和渲染Surface的这套方案的兼容性会不会有问题呢?

实际上,虽然并不常见,但是官方文档里面是有说明可以跨进程进行绘制的:

SurfaceView combines a surface and a view. SurfaceView's view components are composited by SurfaceFlinger (and not the app), enabling rendering from a separate thread/process and isolation from app UI rendering.


并且Chrome以及Android O以后的系统WebView都有使用跨进程渲染的方案。

在我们的兼容性测试中,覆盖了Android 5.1及以后的各个主流系统版本和机型,除了Android 5.x机型上出现了跨进程渲染黑屏的问题外,其余均可以正常渲染上屏和推流。

性能方面:我们使用了WebGL水族馆的Demo进行了性能测试,可以看到对于平均帧率的影响在15%左右,主进程的CPU因为渲染和推流有所升高,奇怪的是小游戏进程的CPU开销却出现了一些下降,这里下降的原因暂时还没有确认,怀疑与上屏操作移到主进程相关,也不排除是统计方法的影响。

9.png

3.7小结一下


为了实现不录制主播端的评论挂件,我们从小游戏渲染流程入手,借助于Surface跨进程渲染和传输图像的能力,把小游戏渲染上屏的过程移到了主进程,并同时生成纹理进行推流,在兼容性和性能上达到了要求。

4、音频采集推流


4.1方案选择


在音频采集方案中,我们注意到在Android 10及以上系统提供了AudioPlaybackCapture方案允许我们在一定的限制内对系统音频进行采集。当时预研的一些结论如下。

捕获方 - 进行捕获的条件:

  • 1)Android 10(api 29)及以上;
  • 2)获取了RECORD_AUDIO权限;
  • 3)通过MediaProjectionManager.createScreenCaptureIntent()进行MediaProjection权限的申请(和MediaProjection录屏共用);
  • 4)通过 AudioPlaybackCaptureConfiguration.addMatchingUsage()/AudioPlaybackCaptureConfiguration.excludeUsage() 添加/排除要捕获的MEDIA类型;
  • 5)通过 AudioPlaybackCaptureConfiguration.addMatchingUid() /AudioPlaybackCaptureConfiguration.excludeUid()添加/排除可以捕获的应用的UID。

被捕获方 - 可以被捕获的条件:

  • 1)Player的AudioAttributes设置的Usage为USAGE_UNKNOWN,USAGE_GAME或USAGE_MEDIA(目前绝大部分用的都是默认值,可以被捕获);
  • 2)应用的CapturePolicy被设置为AudioAttributes#ALLOW_CAPTURE_BY_ALL,有三种办法可以设置(以最严格的为准,目前微信内没有配置,默认为可以捕获);
  • 3)通过manifest.xml设置android:allowAudioPlaybackCapture="true",其中,TargetApi为29及以上的应用默认为true,否则为false;
  • 4)api 29及以上可以通过setAllowedCapturePolicy方法运行时设置;
  • 5)api 29及以上可以通过AudioAttributes针对每一个Player单独设置。

总的来说:Android 10及以上可以使用AudioPlaybackCapture方案进行音频捕获,但考虑到Android 10这个系统版本限制过高,最终我们选择了自己来采集并混合小游戏内播放的所有音频。

4.2跨进程音频数据传输


现在,老问题又摆在了我们眼前:小游戏混合后的音频数据在小游戏进程,而我们需要把数据传输到主进程进行推流。

与一般的IPC跨进程通信用于方法调用不同:在这个场景下,我们需要频繁地(40毫秒一次)传输较大的数据块(16毫秒内的数据量在8k左右)。

同时:由于直播的特性,这个跨进程传输过程的延迟需要尽可能地低,否则就会出现音画不同步的情况。

为了达到上述目标:我们对Binder、LocalSocket、MMKV、SharedMemory、Pipe这几种IPC方案进行了测试。在搭建的测试环境中,我们在小游戏进程模拟真实的音频传输的过程,每隔16毫秒发送一次序列化后的数据对象,数据对象大小分为3k/4M/10M三挡,在发送前储存时间戳在对象中;在主进程中接收到数据并完成反序列化为数据对象的时刻作为结束时间,计算传输延迟。

最终得到了如下结果:
10.png
注:其中XIPCInvoker(Binder)和MMKV在传输较大数据量时耗时过长,不在结果中展示。

对于各个方案的分析如下(卡顿率表示延迟>2倍平均延迟且>10毫秒的数据占总数的比例):
11.png

可以看到:LocalSocket方案在各个情况下的传输延迟表现都极为优异。差异的原因主要是因为裸二进制数据在跨进程传输到主进程后,仍需要进行一次数据拷贝操作来反序列化为数据对象,而使用LocalSocket时可以借助于ObjectStream和Serializeable来实现流式的拷贝,相比与其他方案的一次性接收数据后再拷贝节约了大量的时间(当然其他方案也可以设计成分块流式传输同时拷贝,但实现起来有一定成本,不如ObjectStream稳定易用)。

我们也对LocalSocket进行了兼容性与性能测试,未出现不能传输或断开连接的情况,仅在三星S6上平均延迟超过了10毫秒,其余机型延迟均在1毫秒左右,可以满足我们的预期。

4.3LocalSocket的安全性


常用的Binder的跨进程安全性有系统实现的鉴权机制来保证,LocalSocket作为Unix domain socket的封装,我们必须考虑它的安全性问题。

论文《The Misuse of Android Unix Domain Sockets and Security Implications》较为详细地分析了Android中使用LocalSocket所带来的安全风险。

PS:论文原文附件下载:
The Misuse of Android Unix Domain Sockets and Security Implications.pdf (295.93 KB , 下载次数: 0 , 售价: 1 金币)

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

本帖已收录至以下技术专辑

推荐方案
评论 4
微信团队连个技术总结文章都写的这么详细。。。
只能说微信好用真不是吹出来的
微信客户端架构比较复杂 文章里写的这种情况真是费了劲了
签名: 国庆长假还没有缓过来,请让我静一静,产品狗死远点...
引用:Shahala 发表于 2021-06-22 14:31
微信团队连个技术总结文章都写的这么详细。。。
只能说微信好用真不是吹出来的

微信从发布到现在技术上走的确实是比较扎实的~~
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部