美文网首页
Android弹幕实现现状与原理浅析

Android弹幕实现现状与原理浅析

作者: Android_杜小菜 | 来源:发表于2022-10-23 19:05 被阅读0次

    弹幕实现对比

    目前的弹幕实现方案,主要有以下几种实现方式。

    Android View实现

    通过Android已有的控件实现布局、绘制,高效的话需要自定义缓存逻辑,复用弹幕itemView,主要是text的绘制、样式处理以及位移动画的实现。

    B站Danmaku

    开源的弹幕库:https://github.com/bilibili/DanmakuFlameMaster
    比较成熟稳定,功能齐全,满足现有的所有场景需要,通过canvas绘制。主要功能点:

    • 基础弹幕精确还原绘制、支持mode7特殊弹幕
    • 多核机型优化,高效的预缓存机制
    • 支持多种显示效果选项实时切换、实时弹幕显示支持、自定义字体、换行
    • 运动弹幕、多种弹幕参数设置、弹幕屏蔽
      项目缺点:
      1,没有实现OpenGL ES的绘制,官方库一直没有更新维护,这个绘制方式是TODO状态;
      2,SurfaceView和TextureView的实现有一些问题:焦点问题导致的弹幕不显示,TextureView也无法显示弹幕(官方demo也存在问题,主要是setSurfaceTextureListener设置监听了也没有任何回调,这就很伤),待分析解决。

    AkDanmaku

    https://github.com/KwaiAppTeam/AkDanmaku
    KS推出的弹幕库,支持基本的弹幕处理和显示、倍速播放等;支持弹幕悬停等交互响应操作;支持丰富的弹幕文本样式,以及自定义样式扩展;性能良好,方便扩展,支持丰富的运动动画效果,尤其是弹幕动画,并支持自定义渲染。
    缺点:
    1,需要引入一个游戏引擎渲染库(libGDX - Java跨平台游戏开发框架打造出 Android Native 图形处理框架)
    2,会增加多个平台的so文件,增加问题定位的难度;
    3,对现有的项目改动较大,上层的View,数据层,弹幕管理都需要一定的改造;
    4,兼容性和稳定性,未在我们项目中线上实践,暂未知。不过由于底层依赖的引擎开源且比较稳定,问题应该也不大。

    OpenGL实现

    可以将上层cavans绘制好的bitmap上传到GPU绘制,即使用OpenGL来实现更高效的绘制、平移动画。针对我们项目的特点,我认为目前比较合适的实现方案:基于B站弹幕库已有逻辑的基础上,补充OpenGL的渲染实现。目前B站开源库并没有OpenGL的实现,我认为可行,但有一定的难度。AkDanmaku的上层也是canvas绘制,底层也是OpenGL去绘制。这是我目前比较推荐并且接下来将尝试实现的方案,后续再补充文档。

    Danmaku实现原理

    支持的样式非常丰富,动画也是有姿有色,不限于水平移动,还支持各种复杂动画,支持弹幕重叠,隐藏、显示,停止、播放,支持纯文本,也支持图文混排。

    B站弹幕库实现demo

    1,bitmap处理

    1,弹幕很重要的一个点就是bitmap的处理,并且通过缓存bitmap可以更高效的实现弹幕缓存。这个库对bitmap的处理使用了ndkbitmap库:bitmap = NativeBitmapFactory.createBitmap(w, h, config);
    libndkbitmap.so(ndk)源码:https://github.com/Bilibili/NativeBitmapFactory
    Java_tv_cjump_jni_NativeBitmapFactory_createBitmap,通过在native heap上创建Bitmap,会减小java heap的压力,避免OOM。

    2,实现流程和主要类

    Danmaku流程

    各个类说明:

    1. R2LDanmaku : 一个弹幕对象, 里面包含x、y坐标, 缓存的Bitmap等属性。
    2. DanmakuView : 用来承载弹幕显示的View,普通view的绘制方式,所以DanmakuView在有大量弹幕时Graphics占用内存会比较多。
    3. DanmakuSurfaceView:单独开辟一个Surface来处理弹幕的绘制操作,即绘制操作是可以在子线程(DrawHandler),不会造成主线程的卡顿,即双缓冲绘制,内存及耗电量低、绘制及时,但是sdk24以下不支持动画和截图。
    4. DanmakuTextureView:同上,但能像普通View那样能被缩放、平移,也能加上动画(SurfaceView需要android7.0以上才支持),TextureView doesn't support displaying a background drawable。内存和耗电稍高、绘制会有1-3帧的延迟、但是支持动画和截图。
    5. DrawHandler : 一个绑定了异步HandlerThread的Handler, 控制整个弹幕的显示逻辑。DrawHandler调度引起DanmakuView的渲染。
    6. CacheManagingDrawTask : 维护需要绘制的弹幕列表, 控制弹幕缓存逻辑;将弹幕添加到弹幕集合danmakuList中;CacheManagingDrawTask.CacheManager创建弹幕缓存DrawingCache
    7. DrawingCacheHolder : 弹幕缓存的实现,缓存的是Bitmap, 与BaseDanmaku绑定。
    8. DanmakuRenderer : 对弹幕做一些过滤、碰撞检测、测量、布局、缓存等工作。
    9. Displayer : 持有Canvas画布, 绘制弹幕。
    10. 通过Choreographer来不断驱动渲染DanmakuView。

    3,目前的问题

    1,性能稍微有点问题
    目前的版本可能存在ANR,内存泄露和多线程问题,渲染的性能有待提升。
    2,帧率偏低
    早先未做任何弹幕优化的分支,mock大量弹幕,很多帧率在40以内,不过这个跟房间其他逻辑的影响关系比较大,合并公屏和房间性能优化,这个问题会有明显改善。这个属于周边环境治理,主推做法还是用SurfaceView渲染,双缓冲会更稳,弹幕显示不容易受主线程卡顿的影响。

    3,使用其他渲染View却有问题
    直接换成DanmakuSurfaceView后,弹幕帧率会显著提升且比较稳定,但是有bug需要解决:如层级问题(仅在视频上方一级问题也不大,否则就只能TextureView展示,但是这个性能相对差,所以相比性能来讲,可以业务回避层级问题),偶现闪烁消失的问题(两个SurfaceView打架,抢焦点的问题)。
    换成DanmakuTextureView,弹幕居然无法显示(官方demo也是如此,待研究)。
    4,弹幕管理是否合适?

    所以需要找出原因并解决以上问题,才好拓展OpenGL的实现(因为OpenGL需要基于GLSurfaceView或者GLTextureView实现,也会有3的问题,所以需要先处理已有的问题)。

    AkDanmaku

    AkDanmaku 弹幕引擎结合了移动平台原生渲染流程和游戏引擎的架构理念,在提供完备功能和高性能保障的前提下,提升了扩展性,降低了维护成本,使得开发高级弹幕特效功能变得更容易。该引擎主要针对移动平台短视频播放中的弹幕场景提供包含弹幕流程与数据处理、弹幕展现以及特效处理等功能的全场景解决方案,具备功能全面、高性能、易扩展和维护等特性。更多信息可以看github介绍。

    主要功能

    1. 基于libGDX - 跨平台 Java 游戏开发框架和 ECS 架构实现 Android Native 图形处理框架,提供上层的弹幕 UI 和动效处理等能力。
    2. 多线程调度模型,将渲染、逻辑处理分离,保证性能。
    3. 具备缓存机制,提升性能和空间利用率
    4. 支持基本的弹幕处理和显示能力、倍速播放等
    5. 支持弹幕悬停等交互响应操作
    6. 支持丰富弹幕的文本样式,以及自定义样式扩展
    7. 支持丰富的运动动画效果

    libGDX 是一个基于 OpenGL (ES) 的跨平台 Java 游戏开发框架,适用于 Windows、Linux、macOS、Android、浏览器和 iOS。 它为快速原型设计和快速迭代提供了一个久经考验的稳健环境。所以基于这个实现,就已经具备良好性能的基础。

    弹幕效果

    AkDanmaku效果

    这个demo效果,跟B站demo的效果相比,基本上功能都能对应实现。

    工作流程图

    AkDanmaku流程图

    SDK 层中 DanmakuView 与 DanmakuPlayer 的设计参考了 Android 原生平台的视频播放场景(VideoView & MediaPlayer)的 API 设计。
    底层内核 DanmakuEngine 包含基于 libGDX 框架实现的 ECS 管理器,负责建立、维护 ECS 系统,并作为弹幕 API 的内部调用实现。其中,act() 与 draw() 方法分别为弹幕运算与绘制的入口。

    多线程调度

    1. 运算流程:ActionThread 负责流程中的数据处理和逻辑运算,最终的处理结果转化为可用于显示环节直接使用的 RenderObject List,并通知系统进行下一帧的刷新。
    2. 绘制流程:RenderThread 专注于弹幕绘制,通过拿到运算流程的结果数据 - RenderObject List 直接调用原生平台的绘制 API 进行弹幕绘制,并通知引擎准备下一帧的结果运算。
    3. 缓存流程:CacheThread 专注于整个流程中的缓存处理。

    初步研究

    使用AkDanmaku的话,只有继承View的DanmakuView用于UI展示,重写onDraw(canvas: Canvas) 方法,将canvas丢给danmakuPlayer?.draw(canvas)绘制。
    但是这个绘制,怎么跟libGDX交互的,需要了解libGDX是怎么工作的才能明白。


    image.png

    初步测试,整体性能是ok的,能满足现有的功能需求,比较流畅,弹幕帧率60+,也方便拓展。不过肯定也会有一些问题。

    未来计划

    1,针对B站弹幕库,尝试修复多个SurfaceView同时出现时弹幕的层级问题。
    2,将Danmaku源码扩展OpenGL渲染。
    3,基于AkDanmaku继续测试研究,对比效果、测试性能。

    仅供学习交流,如有问题,请留言联系杜小菜。

    相关文章

      网友评论

          本文标题:Android弹幕实现现状与原理浅析

          本文链接:https://www.haomeiwen.com/subject/gqefyrtx.html