首页 文章 精选 留言 我的

精选列表

搜索[语聊社交],共5955篇文章
优秀的个人博客,低调大师

人工智能语聊的相关原理学习(二)

导读 前段时间楼主决定潜心研究google的word2vec中使用到的相关原理,但是在一番学习之后,楼主发现作为一枚小白,如果对自然语言处理的基础原理都不了解的话,实在是很难理解word2vec中的那些算法/公式等等,于是楼主又转而开始学习自然语言处理中用到的那些基础方法和原理。那么今天,楼主就以一枚小白的身份来介绍介绍自然语言处理中的一些基础概念和原理,当然,概念和原理很多,楼主这里先介绍的是其中看起来比较基础部分,也是自己感觉能够理解明白的一部分,其他内容,会在系列文章的后续介绍。在此也希望路过的大神以及同样对自然语言处理感兴趣的同学多多交流学习,共同成长。 自然语言处理经历的两个阶段 自然语言处理始于上世纪50年代,起初,研究自然语言处理的前辈们认为,如果要让机器能够和人进行语言交流,那么首先需要让机器能够理解人类的语言,也就是说,机器需要了解词性/词义/语法/文法等等这些规则,是基于语义规则进行的自然语言处理,通常也被称为“鸟飞派”。然而,这种基于语义规则的自然语言处理研究很快就遇到了瓶颈,因为本身人类语言所涉及的那些规则就非常非常多,在同种语言内就有很多规则是在特定场景下才有效的,且规则与规则之间还存在冲突,在编写规则的时候需要定义该规则的适用场景,而语言本身又在不停的发展,要增加或者修改某一规则的同时还会涉及到其他很多规则的连带改动。而不同的语言又是基于不同的规则的,在不同语言之间这种基于语法规则的自然语言处理根本就没有通用性。以上种种原因,使得基于规则的自然语言处理很快就停滞不前了。到了70年代,有人提出了基于统计的自然语言处理方法,这个人就是“贾里尼克”,这种自然语言处理方法有效的避开了上述问题,并且获得了众多自然语言处理科学家们的认可。但是新老交替的过程及其漫长,从70年代一直持续到了90年代,期间的原因这里就不多掰了,无非就是新生力量在老一辈没有倒下之前一直被打压的故事等等。接下来,我们就来看看基于统计的语言模型是怎么样的。 统计语言模型 基于统计的语言模型,最主要体现在统计和概率上,大体思路可以通过下面这个例子来介绍:假设 S 表示一个句子,W1,W2 ... Wn 则表示句子 S 中第1到第n个词,P(S)就表示 S 这个句子在所有的语料中出现的概率。它的概率就是W1,W2 ... Wn同时出现的概率,于是有以下公式:P(S) = P(W1,W2,…,Wn)而W1,W2 ... Wn同时出现的概率又可以由下面的公式得到:P(W1,W2,…,Wn) = P(W1)·P(W2|W1)·P(W3|W1,W2)…P(Wn|W1,W2,…,Wn-1)这里先解释一下表达式 P(A|B) 的含义:B表示前提条件,A表示概率对象,整个表达式表示在B存在的条件下,A出现的概率。所以上面的表达式代表:W1作为句首出现的概率乘以W1出现时W2也出现的概率乘以W1和W2同时出现的情况下W3也出现的概率...以此类推,一直乘到W1,W2...Wn-1都出现的情况下Wn也出现的概率。那么这个概率又是怎么来的呢,这里就要依赖我们的预料库了,语料库是很大很大的一堆语料文本,上面说的概率就是去计算在这个语料库中,这些词出现的概率,所以语料库的大小和质量,直接决定了自然语言处理的准确度。P(W1)·P(W2|W1)·P(W3|W1,W2)…P(Wn|W1,W2,…,Wn-1) 从这个公式我们可以看出,在语料库特别大,S又是一个长句的情况下,这个公式的计算量是非常非常大的,几乎可以说是不可能算出来的。那怎么办呢?这时候,数学家们的神通本领就显现出来了。这里用到的是“马尔可夫假设”,马尔可夫假设是俄国数学家马尔可夫在19世纪末20世纪初提出的一种处理上述情况的方法。即当上述情况出现时,我们可以假设某一个元素的出现只和它的前一个元素相关,于是上面的表达式可以变成:P(W1)·P(W2|W1)·P(W3|W1,W2)…P(Wn|W1,W2,…,Wn-1) = P(W1)·P(W2|W1)·P(W3|W2)…P(Wi|Wi-1)…P(Wn|Wn-1)表达式变成上面这样以后,我们可以看出,整体的计算量要比之前小了很多很多,计算机很容易就可以算出。上面的公式就是我们统计语言模型常说的二元模型(Bigram Model)。这种马尔可夫假设,也被称为二阶马尔可夫假设。为了使计算变的更加准确,我们还可以使用N-1阶马尔可夫假设来进行处理,即认为一个词出现的概率和它前面的N-1个词相关,这也就是我们常说的N元模型(N-gram Model)。N元模型的空间复杂度为:时间复杂度为:从目前工业上的应用情况来看,当N=3时,较二元模型的处理效果要显著上升,但当N=4或者更大时,这种上升的效果就没有那么明显了,而且对于计算资源成本来说是陡增的,所以,目前工业上的应用应该大部分都选择的是N=3的N元模型。 中文分词 中文分词在整个自然语言处理学科中来说,应该说是相对简单,而且也是已经做的比较好的一个部分了,这里就简单点介绍一下。最初的中文分词是基于“查词典”的方式来做的,即准备一个中文辞典,在输入一句话以后,系统从左至右进行一轮扫描,基于长词优先的原则,或者是按照最少词划分原则等等,将一句话中的词分割开来。通过这些手段进行的分词方法,基本可以处理大部分的分词情况了。但是在一些复杂的情况下,这种分词方法就显得不是那么的有效了,举下面的例子来说明一下:发展/中国/家 按照字典查询的方式,“中”本应该是作为一个单独的字,但是按照字典查询,它和后面的“国”组成了“中国”,然而整个句子的分词是不对的。上海大学/城/书店 按照长词优先的原则,“上海大学”被截成了一个长词,而原本的划分应该是:“上海”/“大学城”/“书店”。如何解决上述的这些问题呢?其实很简单,利用统计和概率来判断就好了。举个例子,如果一个句子,按照各种分词的方法,可以分成以下三种情况:A1,A2,A3,…,AkB1,B2,B3,…,BkC1,C2,C3,…,Ck那么哪种分词才是最合理的呢?我们利用上一个章节讲到的统计语言模型来解决这个问题,当出现以下这种概率情况的时候,那么按照A的方法来进行分词,一定就是我们最优的分词方法了。P(A1,A2,A3,…,Ak) > P(B1,B2,B3,…,Bk)P(A1,A2,A3,…,Ak) > P(C1,C2,C3,…,Ck) 逆文本频率值TF-IDF 这个章节我们来聊一聊逆文本频率值(TF-IDF)。先来解释一下字面意思:TF:Term Frequency 即词频。IDF:Inverse Document Frequecy 逆文本频率指数(数学家取的名字,确实很难理解)。IF-IDF 简单点可以理解为是加权重的词频。那么这个东西是用来干什么的呢?我们继续举例子来说明。假设,我在进行网页关键字搜索的时候,“原子能的应用”这个关键字,一定会有很多可以匹配到的网页,那么搜索引擎应该返回哪些结果呢?按照什么顺序来返回呢?把哪些搜索结果放在前面呢?这里其实有很多需要考虑的因素,其中“查询相关性”就是一个非常重要的因素。而这个TF-IDF就是可以用来进行查询相关性的计算的。什么是查询相关性呢?我们接着刚才那个例子来讲。假如现在查到了很多个网页上面都包含了“原子能的应用”这组关键字(当然,一般是按照分词后的结果来进行查询的,即“原子能/的/应用”)。那么如果有一个结果网页上一共有1000个词,在这个网页上,“原子能”出现了2次,“的”出现了35次,“应用”出现了5次。则它们在该网页上的的词频分别为0.002,0.035,0.005,于是,我们可以简单的认为“原子能的应用”这组关键字和这个网页的查询相关性为:(0.002+0.035+0.005)=0.042但是这个简单的方法中有一些问题:1. “的”这个关键字在整个相关性中贡献了大部分的比重,而“的”这个关键字其实对于主题相关性的判断几乎是没有意义的;2. “应用”这个关键字也贡献了0.005,比“原子能”的贡献高了2.5倍,但其实这组关键字中,“原子能”其实才是和主题相关性最强的。所以,我们需要在这个简单的以词频来衡量的相关性中加上词的权重来重新计算查询的相关性。方法如下:为查询关键词设置相关性权重,权重的设定需满足以下条件: 一个词预测主题的能力越强,权重越大,反之,权重越小; 停止词的权重为零。 假定一个关键词 W 在 Dw 个网页中出现过,那么 Dw 越大,则词 W 的权重越小,反之亦然。计算公式:其中D为全部网页数。举个例子来看下:例:假设所有的中文网页数为10亿,如果“的”在所有的网页中都出现过,那么它的IDF = log(10亿/10亿) = 0“原子能”在200万个网页中出现过,那么它的IDF = log(500) = 8.96“应用”在5亿个网页中出现过,那么它的IDF = log(2) = 1那么和该网页的查询相关性公式应该从简单的词频相加优化成:TF1*IDF1+TF2*IDF2+...+TFn*IDFn即:0.002*8.96+0.035*0+0.005*1=0.0161现在再来看这个方法得出的网页相关性以及每个词的贡献值,就要合理得多了。这个方法最早是用来进行关键字搜索相关性计算的,在自然语言处理中自然也能用来搜索对话内容匹配答案等等。 向量的相似度 上表的意思先解释一下,表中的第一列就是一个大的词典中的词编号,可以看到共有64000个词。第二列是这个词典中具体的词。第三列呢,它代表一篇文章,但它不是我们可以看懂的文章,而是给计算机去读的一篇文章,它的每一行代表了词典中的这个词在这篇文章中的逆文本频率值(TF-IDF)。那么第四列,则是另一篇文章了,第五列,就是第三篇文章。我们可以看出,代表文章的每一列,其实是一组64000个元素的超大数组,在自然语言处理中,我们把它叫作一个向量,是一个代表一篇文章的64000维的向量。那么文章的向量是个什么鬼呢?它是要用来干嘛的?回想一下上一个章节所讲的内容,TF-IDF是用来表示主题相关性的一个指标,那么这个文章的向量呢,其实也就是代表一篇文章的主题(当然,一篇文章可能涉及多个主题,但是没关系,都可以在这个向量中体现出来)。一个向量是从空间中的一个点指向另一个点的一段距离,所以,它有两个特征,一个是距离,一个是方向。对于长度非常长的文章来说,它的向量距离则会更长,而反之则越短,而文章的长度和主题其实并没有什么关系,所以向量的长度自然也不会和文章的主题有什么关系。那么向量除了距离之外的另一个属性,则是方向了,如果文章的主题是相似的,则两个代表文章的向量它们的方向也将是相似的(这一点我们把文章向量的维度降维到2维/3维应该很容易就看出来了,这里就不多掰了)。于是,我们可以通过判断两个文章向量的夹角大小来判断,这两篇文章是否是主题相似的。利用余弦定理:有如下三角形则角A的余弦:把两边b,c看成是两个以A为起点的向量,则上面公式等价于:假如文章A和文章B对应的向量分别为:那么,它们夹角的余弦等于:上述公式中,分子分母都是正数,所以结果是在0-1之间的,根据余弦定理我们可以知道,当两个向量相似的时候,它们的夹角将是较小的,最后的结果将接近1,说明它们是主题相似的两篇文章,当两个向量重合时,上式的结果等于1,说明它们是主题完全相同的两篇文章。而当两个向量夹角比较大的时候,甚至相互垂直的时候,结果则是趋于0的。自然语言处理中用这种向量的方式来计算两个语言对象的相似性,它们可以是词向量,句向量或者文章向量,划分向量的维度可能不同,但是计算相似性的原理是一样的。 矩阵运算中的奇异值分解 最后一部分来聊一下矩阵中的奇异值分解,这个东西嘛,个人感觉是对自然语言处理中工程化进行简化的一种算法,它可以将超大的矩阵运算量成倍的降低,也算是自然语言处理工程化中非常重要的一个部分。首先来看一下下面这个矩阵:我们假设,有N个词,M篇文章,组成矩阵A,矩阵每行对应一篇文章,每列对应一个词,矩阵中的每个元素代表字典中第j个词在第i篇文章中出现的加权词频(如TF-IDF)。假如 M=1,000,000 N=500,000,则矩阵中共有5000亿个元素,设想一下,要对这样一个矩阵进行运算,首先,这个矩阵的存储就是一个很困难的事情,要进行运算的话,那就更不要提了。于是,数学家们提供了一种有效的方法来对这个矩阵进行处理,方法如下:首先,将矩阵A分解成三个小矩阵X,B,Y。X:对文章进行分类的结果,每行表示一篇文章,每列表示一个文章主题,一行中每个元素表示这篇文章和各个主题的相关性;Y:对词进行分类的结果,每列对应一个词,每行对应一个语义类,一列中的每个元素表示这个词和每个语义类的相关性;B:表示词的语义类和文章主题之间的关系。于是,矩阵A被分解成了如下三个 1,000,000*100 / 100*100/100*500,000 的较小矩阵,虽然这三个矩阵仍然不小,但是它们的全部元素加起来,也不过1.5亿,只有矩阵A元素的1/3000不到,相应的存储量和计算量也都会小三个数量级以上。这种方法,在矩阵运算中被称作奇异值分解。 小结 本次分享到此就结束了,对于楼主来说,像线性代数啊,余弦定理啊这些东西,早在很多年前就已经还给老师了,所以这次分享也就不涉及到这些公式的推导,过程的证明了(这些都是数学家们的事情,估计以前学的数学就算记得可能也不怎么管用),纯原理性的东西分享,希望可以和大家多多交流。走过路过的老司机们,看到讲的不对的地方,请不吝纠正。

优秀的个人博客,低调大师

在元宇宙中搭建语聊房,设计思路和实现方法分享

目标 1. 制定目标 不妨大家想一下这个场景: 你走进一个咖啡厅,看到了一个美女或者帅哥,想要一个微信号,方便后续发展。 那我们要怎么做呢? 走到对方跟前,小声对他说:“你好,你长的很像我的前女友(前男友),可以加下你的微信吗?”。 对方有可能悄声的说,“我扫你”。 你这个时候你就需要把你的二维码展示给他。 当你们恋爱成功,求婚的时候,可能要找一个更加浪漫的环境,并且你们的对话希望给更多人看到、听到。 通过这个上面场景的想象,我们可以简单的确定一些基本的要素 可以更改的场景 (咖啡厅和求婚现场) 自主塑造形象功能(毕竟外貌是一见钟情的原点) 单聊 (要微信号不能一次要一群人得呀。不然可能被当成流氓给打得哟) 文件共享 (把自己得二维码展示给对方,让对方加好友) 群聊、群视频 (把求婚现场给其他用户看) 2. 拆解目标 为了满足上面提到得要求,我们需要以下功能: 支持unity的方便接入。毕竟unity是一个非常好的3D引擎,如果可以方便接入得话,在后续得场景创建和人物创造上都可以有更大的扩展空间 1 vs 1的视频,或者 1 vs 多的直播模式。可以满足单聊和群聊的需求 同步本地播放的本地视频或者远端视频给其他用户。可以满足共享二维码的需求。 3. 效果预览 如果要完整的实现一个完整的项目会花费比较多的时间,而我发现目前声网的 Demo 会提供简单的人物模型和素材,但是他们开放度较高,开发者可以根据需要使用自己定制化的模型素材,为了实现上述提到的需求,我只做了一些微小的修改,就满足了我们的需求。本次主要看下效果,为后续在生产环境中落地做一些技术上的储备。 以下是在demo上调整后显示出来的效果。 这是用unity来实现的一个咖啡厅的样子 这是一个服装店,我们可以给人物进行换装,这个也是由unity来实现的。 以上两个的接入都非常的方便,我们可以很好的对接unity,如果需要的话,后续有自己的unity场景也可以快速接入。 这个是我自己测试的1 vs 1的视频测试效果 这个是我自己测试的将本地视频分享给对方的一个效果 想象一下,是不是可以和你的对象一起看剧了呢? 简单实现 以上效果都是基于声网的sdk来实现的,如果你也想进行尝试或者使用,可以继续查看我的接入过程和中间遇到的问题以及解决方案。 1. 账号准备 我们既然使用了声网的sdk,那注册一个声网的账号做一些声网的账号配置也很合理吧。 注册账号 完成实名 创建项目 配置项目 然后进入详细配置 生成token,并将appid、证书、token复制保存。 注意!!!这里很重要,一定要看仔细,项目中会使用到! 至此,账号的准备已经完成了。 2. 配置项目 demo源码 通过声网官网客服,申请元宇宙 SDK、Unity工程文件、开发指南,同时申请开通使用权限,详情可访问 shengwang.cn 将sdk解压并配置到项目中 一定要注意目录,如果自己的项目中不存在当前的目录要自己创建 配置id一般情况下,这个文件是不会上传到远程仓库的,写到这里也主要是安全考虑。将在后台中申请的appid、证书,复制后粘贴到/Android/local.properties里面,如下: 配置channel 将在后台生成token的时候,填写的channel给写到类中,如下 配置项目权限 <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <!-- 对于 Android 12.0 及以上设备,还需要添加如下权限: --> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> 注意: Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA 需要动态申请权限,否则无法正常完成视频功能。 sdk本身是不会去申请权限的,所以需要我们自己在合适的位置帮sdk申请好权限。 混淆配置 咖啡厅 做形象 服装店 这里是直接使用了声网给的效果,如果自己项目需要的话,可以去自己去调整。 其中换装和捏脸都需要unity上的一些修改和native与unity的通信。整个可以参考换装和捏脸 3. 视频1vs1 前面已经完成了整体项目的构建。并且已经把unity内容跑起来了,那么接下来我们看下,如果要实现 1vs1的视频要怎么处理。为了方便处理,我单独写了一个页面来处理视频1vs1,效果如下: 视频聊天 这个流程图,建议大家多看几遍,在我们遇到问题的时候,这个图会给我们启发,帮助我们解决问题。 详细流程: 1. 创建RtcEngine对象 该对象管理了整个的视频等场景,是一个非常核心的对象。 try { RtcEngineConfig config = new RtcEngineConfig(); config.mContext = getBaseContext(); config.mAppId = appId; config.mEventHandler = mRtcEventHandler; config.mAudioScenario = Constants.AudioScenario.getValue(Constants.AudioScenario.DEFAULT); mRtcEngine = RtcEngine.create(config); } catch (Exception e) { throw new RuntimeException("Check the error."); } 2. 创建RtcEngine属性 // 视频默认禁用,你需要调用 enableVideo 启用视频流。 mRtcEngine.enableVideo(); // 录音默认禁用,你需要调用 enableAudio 启用录音。 mRtcEngine.enableAudio(); // 开启本地视频预览。 mRtcEngine.startPreview(); 3. 将本地摄像头内容显示到local_video_view_container上 FrameLayout container = findViewById(R.id.local_video_view_container); // 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象。 SurfaceView surfaceView = new SurfaceView (getBaseContext()); container.addView(surfaceView); // 将 SurfaceView 对象传入声网,以渲染本地视频。 mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, KeyCenter.RTC_UID)); 4. 设置当前的模式 ChannelMediaOptions options = new ChannelMediaOptions(); // 将用户角色设置为 BROADCASTER。 options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER; // 视频通话场景下,设置频道场景为 BROADCASTING。 options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING; 其中clientRoleType有两种,如果是CLIENT_ROLE_BROADCASTER就是可以播和收,如果是CLIENT_ROLE_AUDIENCE就只能收看,当前就成了主播模式。 5. 加入频道 // 使用临时 Token 加入频道。 // 你需要自行指定用户 ID,并确保其在频道内的唯一性。 int res = mRtcEngine.joinChannel(token, channelName, KeyCenter.RTC_UID, options); if (res != 0) { // Usually happens with invalid parameters // Error code description can be found at: // en: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html // cn: https://docs.agora.io/cn/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html Log.e("video","join err:"+RtcEngine.getErrorDescription(Math.abs(res))); } joinChannel方法有返回值,可以看到我们加入频道是否成功了,如果不成功的话,我们可以看下错误原因,并对照解决;如果成功了,就可以观察IRtcEngineEventHandler对象的回调方法,重点关注下 onError(int err) 和onJoinChannelSuccess(String channel, int uid, int elapsed) 如果收到 onJoinChannelSuccess方法的回调,我们就可以关注 onUserJoined(int uid, int elapsed) 方法,我们可以在这个方法里开始显示远端内容。 6. 显示远端内容 @Override // 监听频道内的远端主播,获取主播的 uid 信息。 public void onUserJoined(int uid, int elapsed) { Log.e(TAG, "onUserJoined->" + uid); runOnUiThread(new Runnable() { @Override public void run() { // 从 onUserJoined 回调获取 uid 后,调用 setupRemoteVideo,设置远端视频视图。 setupRemoteVideo(uid); } }); } private void setupRemoteVideo(int uid) { FrameLayout container = findViewById(R.id.remote_video_view_container); SurfaceView surfaceView = new SurfaceView (getBaseContext()); surfaceView.setZOrderMediaOverlay(true); container.addView(surfaceView); mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid)); } 最终,远端的视频会显示在 remote_video_view_container 上。 4. 将本地播放的视频显示给远端用户 同步本地播放给远端 将本地视频怎么同步给远端的用户呢?其实从本质上来讲,将本地摄像头和将本地播放的视频给远端用户看,对远端用户都是一样的,不一样的是本地用户给远端用户的数据源是哪个。一个是摄像头,一个是播放器。 设置本地video的时候做下修改 FrameLayout container = findViewById(R.id.local_video_view_container); // 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象。 SurfaceView surfaceView = new SurfaceView (getBaseContext()); container.addView(surfaceView); // 将 SurfaceView 对象传入声网,以渲染本地视频。 VideoCanvas videoCanvas = new VideoCanvas(surfaceView, Constants.RENDER_MODE_HIDDEN, Constants.VIDEO_MIRROR_MODE_AUTO, Constants.VIDEO_SOURCE_MEDIA_PLAYER, mediaPlayer.getMediaPlayerId(), KeyCenter.RTC_UID); mRtcEngine.setupLocalVideo(videoCanvas); 在int res = mRtcEngine.joinChannel(token, channelName, KeyCenter.RTC_UID, options);是0的时候,调用如下方法: int res = mRtcEngine.joinChannel(token, channelName, KeyCenter.RTC_UID, options); if (res != 0) { // Usually happens with invalid parameters // Error code description can be found at: // en: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html // cn: https://docs.agora.io/cn/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html Log.e("video","join err:"+RtcEngine.getErrorDescription(Math.abs(res))); }else { mediaPlayer = mRtcEngine.createMediaPlayer(); mediaPlayer.registerPlayerObserver(this); mediaPlayer.open(MetaChatConstants.VIDEO_URL, 0); } 3.监听onPlayerStateChanged回调并在state是PLAYER_STATE_OPEN_COMPLETED的时候执行play方法,代码如下: public void onPlayerStateChanged(io.agora.mediaplayer.Constants.MediaPlayerState state, io.agora.mediaplayer.Constants.MediaPlayerError error) { if(state == io.agora.mediaplayer.Constants.MediaPlayerState.PLAYER_STATE_OPEN_COMPLETED){ mediaPlayer.play(); } } 至此,就完成了功能上的使用。 其它功能 除了上面提到的功能外,声网还提供了一些其他的功能,在需要的时候可以直接使用,或者少量修改就可以用的。 比如说空间音效功能,该功能基于声学原理,模拟声音在不同空间环境中的传播、反射、吸收效果。可以为用户提供旅游中路人聊天声、海浪声、风声等,让用户更沉浸式体验 再比如说实时共赏影音功能。该功能可以几乎无延时的实现,远程观影、听歌等功能。甚至可以实现k歌的能力。 更多的功能期待大家一起发掘! 参考资料 注册声网账号 声网官网 SDK下载 快速开始 - 实现音视频通话 媒体播放器

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册