cocos creator 方法数组_基于 Cocos 游戏引擎的音视频研发探索

本文转载自公众号:流利说技术团队(lls_tech)

版权归原作者所有

a7f3e01523ee2bd5f2821d018a96e1bc.png

本文主要介绍了流利说团队基于 Cocos 游戏引擎进行音视频相关需求开发过程中所遇到的问题和解决方案。文章中将依次阐述 Cocos 引擎直接渲染视频的方案,继而引申出多线程环境下 OpenGL 环境的管理方法,最后说明音视频处理流水线模型需要解决的问题与我们的方案。

让 Cocos 引擎直接渲染视频

为什么?

可能大家首先会疑惑,为什么要让 Cocos 引擎来负责渲染视频呢?而不利用原生平台的渲染机制,如使用Android平台的SurfaceView或TextureView。

让我们来分析下利弊:

原生机制优势:1.常规播放器接口的直接支持。2.视频渲染性能稳定。3.代码简单。

原生机制劣势:1.无法更精确的调整视频与游戏元素的层级关系(只能置游戏其上或其下)2.游戏控制播放器动画有性能损耗(游戏→ native 损耗)。

因此,如果要在视频层上播放游戏显示游戏元素或游戏动画,利用原生机制无疑会转移大量的游戏业务在native端完成。这无论从项目管理和实现复杂度考虑都是不可取的。因此我们决定让 Cocos 引擎使用外接纹理渲染视频。

实现方案

分析 Cocos 引擎本身的渲染机制,我们发现cocos引擎中封装了cocos2d::Sprite 对象用于渲染显示,Sprite对象需要我们提供 cocos2d::Texture2D 对象和尺寸信息。因此我们可以封装一个 Cocos 引擎中的基础节点 cocos2d::ui::Widget 专门用于视频显示,Cocos 中 widge 对象是类似于 native 中 View 功能的组件,负责管理组织绘制大小,绘制位置和绘制内容。我们可以在其回调的 Draw 方法中完成视频绘制。

在 native 层我们将视频输出数据转为纹理,再传递至Widget中转化为 Cocos 的Texture2D对象交由 Sprite 绘制。而 Sprite 渲染尺寸则由 widget 提供。由此我们可以得出下面这样一个简易的转化链:

19742246dc131d614caf2442de60859d.png

多线程 OpenGL

但是上面这条转化链并不能简单的实现。首先 Cocos 引擎是在单独开启的一个线程中进行工作的,以下简称 Cocos 线程。也就是说我们最终 OpenGL 的绘制都会在 Cocos 线程中操作。我们用 Cocos 线程的 OpenGL context 去进行纹理转化,甚至增加贴图美颜等功能都是不合适的。音视频中有一些 OpenGL 操作,很有可能使 Cocos 整个 OpenGL 状态机被破坏掉。所以需要将所有的音视频转化、处理操作都限制在子线程中。

假如我们需要在多线程下共享纹理数据,需要让 OpenGL Context 共享同一个ShareGroup。因此我们需要接管整个架构环境中所有 OpenGLContext的构建过程。如Android端我们需要在 Cocos 引擎Cocos2dxActivity的中将 Cocos2dxGLSurfaceView::setEGLContextFactory 修改为我们自己的提供的方法。除此之外,纹理转化和处理模块的 OpenGL 环境也需要统一构建共享 ShareGroup 的 context。

Android端有一点特殊之处,屏蔽了 ShareGroup 的概念。但是我们只要在 OpenGL Context 的构造函数传入一个 Context,即可让两个 Context共享 ShareGroup。

5969af51b334a33172392babbe132694.png

音视频处理流水线模型

建立模型

为了整合音视频处理的各个环节,构建统一的错误处理、线程管理、生命周期管理机制,我们对音视频处理流程进行了抽象,建立起一个以音视频源、线程分发器、消费者链组成的流水线模型。

734cdd13ad6e2308e41d6d11bc740488.png

抽象出的音视频源负责加载本地或网络视频资源,而后进行解码操作。亦或者为采集摄像头数据的采集器,最终输出视频帧数据。而消费者组成消费处理链,负责接收处理帧数据或纹理数据。如我们自定义的 cocos::Widget 可以作为消费链的最后一个消费者。

线程分发器即是负责连接源与消费者。线程分发器创建管理音视频各自的工作线程,把外部命令和音视频数据分发至目标线程再回调给消费者,保证消费者内部方法在同一线程执行,从而消除消费者模块的中的线程安全问题。

按照这样的方式建立的流水线模型具备较好的稳定性和扩展性,可以保证如 OpenGL 上下文管理,视频帧数据转化为纹理等诸多模块的复用。另外由于消费者和生产者的完全解耦,也能够实现诸如动态切换音视频源的功能。此外多线程流水线也能很好的发挥多核 CPU 的性能。

尽管模型已经建立,但在细节方面还存在不少问题等待我们去解决。下面我就简单说明几个问题以及我们的探索。

音视频帧数据的复用

为了避免内存频繁分配而造成的不必要的耗时。我们通常会对构建的之前生成过得音视频数据进行复用,因为存在多分辨率切换的问题,由此会生成诸多大小不一的内存块。因此复用的前提是被复用的内存>=需要分配的内存。在 Android 端即是指 ByteBuffer 的capacity需要满足上述条件。因此我们可以建立一个ByteBuffer对象池用于缓存已经被消费完成的ByteBuffer,在复用时遍历缓存池找寻符合大小条件的 ByteBuffer。在一般情况下视频数据需要 ByteBuffer 数组来存储,因此我们可以对对象池的每个对象增加标签属性,保证相同分辨率的视频数据可以快速找到可被复用的内存。

那么音视频数据被回收的时机是什么呢?单线程模型下是极为容易确定的,但是多线程环境下事情就变得复杂了,我们无法知道什么时候数据才被真正的消费完成。因此我们参考图片加载框架Fresco中对Bitmap回收问题的解决方案引入 Closeable References(可回收引用)概念。CloseableRef 对象包裹我们需要缓存的对象,内置的引用计数会在我们所有线程持有的引用都被 close 后才会回收。在回收的回调方法中我们将其加入缓存对象池中。

工作线程的阻塞监控

开发多线程复杂项目我们必须考虑到在低配机型下,工作线程积攒大量任务无法被消费处理的情况。如音频和视频的处理线程,如果视频处理过慢可能会导致严重的音画不同步。因此我们需要建立可以被管理的工作线程任务队列。我们在 Android 端的实践为:基于 HandlerThread ,另外增加一个可以被管理的Queue。每当产生任务,我们将任务入栈,并向 HandlerThread 发送一条出栈指令,HandlerThread  从 Queue 末尾出栈处理任务。

Queue 中可以记录多项重要参数用于决策处理。如综合任务预期执行时间、任务的处理时间和队列积攒数量进行进行策略性丢弃。或者根据一段时间的综合情况来决定是否降级生产音视频的分辨率等参数等。

总结

以上讲述的几个技术关键点是我们团队在项目开发过程中不断探讨与发现得出的。整套框架方案已经在项目中落地,获得了还不错的开发结果。希望能为大家带来些许帮助。另外敬请期待我们少儿流利说即将上线的直播课功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/557957.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为服务器双系统教程,服务器上安装双系统

服务器上安装双系统 内容精选换一换Atlas 800 训练服务器(型号 9000)安装上架、服务器基础参数配置、安装操作系统等操作请参见《Atlas 800 训练服务器 用户指南 (型号9000, 风冷)》或《Atlas 800 训练服务器 用户指南 (型号9000, 液冷)》。Atlas 800 训练服务器(型号 9000)适配…

线程销毁_多线程(2)-Java高级知识(9)

前面有一篇文章写的是多线程的基本知识,以及线程实现的两种方式,一种是继承Thread类,另一种实现Runnable 接口,今天沿着前面的多线程,继续分享多线程的第三种创建方法,以及第三种创建方法的优势&#xff0c…

怎么查看服务器系统内存,怎么查看服务器系统内存

怎么查看服务器系统内存 内容精选换一换确认服务器服务是否开启。登录虚拟机内部。执行如下命令,查看系统的端口监听状态,如图1所示。netstat -ntplWindows虚拟机可以在命令行中执行netstat -ano查看系统的端口监听状态,或者查看服务端软件状…

as工程放到源码编译_Flutter源码剖析(二):源码的阅读与调试环境配置

综述 Flutter从架构上来说有3部分:用Dart写的Framework层,面向开发者用Java/Kotlin写的Embdder层(For Android,iOS是OC/Swift),纯Flutter App不需要关心用C写的Engine层,提供Dart运行环境和底层绘制能力针对每个部分&a…

workbook加载文件路径_【Python】文件重命名(按照Excel清单)

问题:1、扫描了很多文件,想批量加上文件编号2、网页下载了一堆图片,批量加上图片分类3、文件命名不规范,想批量调整效果图:需求解析:1、想重命名,就要有原名字和修改后的名字,清单准…

ppap文件过程流程图制作_收藏 | 据说PPAP的精华都在这个PPT里!一起来鉴定下

一组数字的PPAP3种情况必须提交8种情况通知提交5种提交等级3种提交状态18份提交内容“3”——以下3种情况,必须向顾客提交PPAP:1)一种新的零件或产品;2)对以前所提供不符合零件的纠正;3)由于设计记录、规范或材料方面的工程变更从…

++实现 ipv6数据报_IPV6报文格式和IPV4有什么区别?

前言RFC2460定义了IPv6数据报格式。总体结构上,IPv6数据报格式与IPv4数据报格式是一样的,也是由IP报头和数据(在IPv6中称为有效载荷)这两个部分组成的。但在IPv6数据报数据部分还可以包括0个或者多个IPv6扩展报头(Extension header)。正文1IPV6报文格式如…

曲面设计步骤pdf_3dmax模渲大师|室外设计师怎么用3dmax疯狂模渲大师制作室内设计效果图的外景天空?...

3dmax模渲大师|室外设计师怎么用3dmax疯狂模渲大师制作室内设计效果图的外景天空?这次食住玩讲的是第十三章的第1节,3dmax疯狂模渲大师室外设计篇的第1课——“外景天空”的使用方法。在室内设计效果图领域,也有要涉及室外的时候。…

iphone日历显示周视图_用敬业签记录放假安排 2021年放假安排日历

2021年已经踏着轻快的步伐向我们一路小跑而来,新的一年中,除了可以做更多的成绩之外,还有很多假期伴随而来。做好放假安排就可以让假期更加充实,为了能够更好的记录放假安排和相关事项可用敬业签这款便签软件。使用敬业签记录放假…

人工智能的炒作_为什么人工智能被过度炒作?

像任何新技术一样,人工智能也经历了称为“炒作周期”的各个阶段。它们从技术触发器开始。如果超过了人们设定的期望值(过度夸大),那么就会带来技术未达到预期的幻灭感,然后就是带来启发的成功案例,最后是生产力的稳定期。有了人工…

opencv轮廓周长原理_opencv轮廓逼近的精度参数与周长cvContourPerimeter的汇总

1)CV_Assert():若括号中的表达式值为false,则返回一个错误信息。函数原型是:#define CV_Assert( expr ) if((expr)) ; else cv::error( cv::Exception(CV_StsAssert, #expr, "", __FILE__, __LINE__) )2)cvContourPerimeter&#x…

mysql的所有聚合函数_MySQL 聚合函数(一)聚合(组合)函数概述

MySQL版本:5.7+ 一、MySQL 5.7中的聚合函数 MySQL 5.7中的聚合函数如下: 除非另有说明,否则组合函数会忽略NULL值。 如果在不包含Group By子句的语句中使用组合函数,就等效于对所有行进行分组。(个人理解是,结果总是只有一行。)关于这点的更多信息,后面的小节“MySQL处理…

mysql 转型_MySQL的未来在哪?

阿里云 MySQL&MariaDB 数据库产品结合开源社区,提供了稳定、可靠、便捷、弹性的在线数据库服务,帮助全球企业客户进行数字化转型。MySQL发展经历了一系列变化,从Sun到Oracle,发展也经过了几个阶段。MySQL从5.7版本开始走上了代…

mongoose换成mysql_如何将MongoDB数据库的数据迁移到MySQL数据库中

FAQ v2.0终于上线了,断断续续忙了有2个多月。这个项目是我实践的第一个全栈的项目,从需求(后期有产品经理介入)到架构,再到设计(有征询设计师的意见)、构建(前端、后台、数据库、服务器部署),也是第一次独立负责一个项目&#xff…

java虚拟机——JVM

JVM:java虚拟机,简称JVM,是运行所有java程序的假想计算机,是java程序的运行环境,是java最具吸引力的特征之一。我们编写的java代码,都运行在JVM之上。 跨平台:任何软件的运行,都必须…

mysql 5.0.37.tar.gz_Linux下MySQL5.0.37安装配置步骤

Linux下安装MySQL5.0.37需要以下面三个包:MySQL-client-community-5.0.37-0.rhel3.i386.rpmMySQL-server-community-5.0.37-0.rhel3.i386.rpmperl-DBI-1.53-2.fc7.i386.rpm(以下步骤需要root权限)1.验证是否已经安装过MySQLrpm -qa|grep MySQL如果发现有安装过,并需要卸载,使用…

JRE和JDK

JRE:是java程序的运行时环境,包含JVM和运行时所需要的核心类库。 JDK:时java程序开发工具包,包含JRE和开发人员使用的工具。 我们想要运行一个已有的java程序,那么只需要安装JRE即可。 我们想要开发一个全新的java程序…

java程序开发步骤

java程序开发步骤 开发环境搭建完毕后,可以开发第一个程序了 java程序开发三个步骤:编写,编译,运行。

thinkphp三级分销小程序源码_山东谷道微信小程序商城源码带后台 公众号平台三级分销系统...

山东谷道微信小程序商城源码带后台 公众号平台三级分销系统那么微信二级分销系统与微信三级分销系统到底有什么区别和联系呢?为什么改了个数字地位就天差地别?1、微信分销模式等级的区别用简洁的话来说,微信的三级分销系统包含了微信的二级分销系统,只…

linux mysql timestamp_MySQL时间类型Timestamp和Datetime 的深入理解

MySQL数据库常用的时间类型有timestamp和datetime,两者主要区别是占用存储空间长度不一致、可存储的时间也有限制,但针对不同版本下,timestamp字段类型的设置需要慎重,因为不注意的可能会被“坑死”。一、TIMESTAMP和DATETIME字段…