关于直播:微信小游戏直播在Android端的跨进程渲染推流实践
本文由微信开发团队工程师“virwu”分享。 1、引言近期,微信小游戏反对了视频号一键开播,将微信降级到最新版本,关上腾讯系小游戏(如跳一跳、欢畅斗地主等),在右上角菜单就能够看到发动直播的按钮一键成为游戏主播了(如下图所示)。 然而微信小游戏出于性能和平安等一系列思考,运行在一个独立的过程中,在该环境中不会初始化视频号直播相干的模块。这就意味着小游戏的音视频数据必须跨过程传输到主过程进行推流,给咱们实现小游戏直播带来了一系列挑战。 (本文同步公布于:http://www.52im.net/thread-35...) 2、系列文章本文是系列文章中的第5篇: 《直播零碎聊天技术(一):百万在线的美拍直播弹幕零碎的实时推送技术实际之路》《直播零碎聊天技术(二):阿里电商IM音讯平台,在群聊、直播场景下的技术实际》《直播零碎聊天技术(三):微信直播聊天室单房间1500万在线的音讯架构演进之路》《直播零碎聊天技术(四):百度直播的海量用户实时音讯零碎架构演进实际》《直播零碎聊天技术(五):微信小游戏直播在Android端的跨过程渲染推流实际》(* 本文)3、视频采集推流3.1 录屏采集?小游戏直播实质上就是把主播手机屏幕上的内容展现给观众,自然而然地咱们能够想到采纳零碎的录屏接口MediaProjection进行视频数据的采集。 这种计划有这些长处: 1)零碎接口,实现简略,兼容性和稳定性有肯定保障;2)前期能够扩大成通用的录屏直播;3)对游戏性能影响较小,经测试对帧率影响在10%以内;4)能够间接在主过程进行数据处理及推流,不必解决小游戏跨过程的问题。然而最终这个计划被否决了,次要出于以下思考: 1)须要展现零碎受权弹窗;2)须要审慎解决切出小游戏后暂停画面推流的状况,否则可能录制到主播的其余界面,有隐衷危险;3)最要害的一点:产品设计上须要在小游戏上展现一个评论挂件(如下图),便于主播查看直播评论以及进行互动,录屏直播会让观众也看到这个组件,影响观看体验的同时会裸露一些只有主播能力看到的数据。 转念一想,既然小游戏的渲染齐全是由咱们管制的,为了更好的直播体验,是否将小游戏渲染的内容跨过程传输到主过程来进行推流呢? 3.2 小游戏渲染架构为了更好地形容咱们采纳的计划,这里先简略介绍一下小游戏的渲染架构: 能够看到图中左半边示意在前台的小游戏过程,其中MagicBrush为小游戏渲染引擎,它接管来自于小游戏代码的渲染指令调用,将画面渲染到在屏的SurfaceView提供的Surface上。整个过程主过程在后盾不参加。 3.3 小游戏录屏时的状况小游戏之前反对过游戏内容的录制,和直播原理上相似,都须要获取以后小游戏的画面内容。 录屏启用时小游戏会切换到如下的模式进行渲染: 能够看到,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对象。 于是咱们有了上面这个初步计划: 能够看到:第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传递到主过程进行渲染上屏吧! 最终咱们大刀阔斧地砍掉了小游戏过程的两个冗余Renderer,MagicBrush间接渲染到了跨过程传递而来的Surface上,而主过程的Renderer在负责纹理类型转换的同时也负责将纹理渲染到跨过程传递而来的小游戏过程的在屏Surface上,实现画面的渲染上屏。 最终所须要的Renderer数量从原来的3个缩小到了必要的1个,在架构更加清晰的同时晋升了性能。 后续须要同时反对录屏时,只有稍作改变,将mp4编码器的输出SurfaceTexture也跨过程传递到主过程,再新增一个Renderer渲染纹理到它下面就行了(如下图所示)。 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. ...