直播相关——声网rtc SDK

声网 SDK项目集成与api使用整理

遥想约4年前,也自行调研过,虽然最终没有在实际项目中落地。 声网Android端集成与一对一音视频功能实现
现在,终于要开始在项目中正式落地了,而声网也从原来的v3.x升级到了v4.x版本了。根据官网介绍,两大版本间改动还是比较大的。本次集成落地,会直接用v4.x版本。
迁移指南

demo

示例项目 API-Examples
跑通 API 示例项目

生成token

进来这个地址
点击总览这里:点击:临时Token生成器生成。(24小时有效期)

水晶球

点击旁边的水晶球,进入水晶球页面,可以查看频道列表
点击对应频道,可以进入其通话详情。可以看到对应用户的uid等信息

集成

发版说明
/app/build.gradle

//声网dependencies {...// x.y.z 替换为具体的 SDK 版本号,如:4.0.0 或 4.1.0-1implementation 'io.agora.rtc:full-sdk:x.y.z'}//implementation 'io.agora.rtc:agora-special-full:4.1.1.26'

这里的集成,可能会存在一些问题。
比如:不同版本可能会有些api用不了;比如说旧版本有些功能有些bug,所以还是使用最新的推荐版本稳妥。

混淆

/app/proguard-rules.pro 文件

#声网
-keep class io.agora.**{*;}

权限

/app/src/main/AndroidManifest.xml

<!--必要权限-->
<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 及以上且集成 v4.1.0 以下 SDK 的设备,还需要添加以下权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<!-- 对于 Android 12.0 及以上设备,还需要添加以下权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>

tip:这里有个需要特别注意的点。如果当前项目中目标版本不是Android 12.0且不是集成 v4.1.0 以下 SDK 的设备。不能加入最后的三个权限。否则部分机型会有闪退问题。目前验证是有一部华为手机鸿蒙4.0的会闪退。
参考链接:快速开始-实现音视频互动

基本流程

创建 RtcEngineConfig 对象,并进行配置。

// 先初始化相关
try {//创建 RtcEngineConfig 对象,并进行配置initRtcEngineConfig()
} catch (e: Exception) {throw RuntimeException("Check the error.")
}private fun initRtcEngineConfig() {// 创建 RtcEngineConfig 对象,并进行配置val config = RtcEngineConfig()config.mContext = baseContextconfig.mAppId = ""config.mEventHandler = mRtcEventHandler// 创建并初始化 RtcEnginemRtcEngine = RtcEngine.create(config)}

启用视频模块

private fun startVideo() {mRtcEngine?.apply {// 启用视频模块this.enableVideo()// 开启本地预览this.startPreview()// 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象val container: FrameLayout = findViewById(R.id.local_video_view_container)container.removeAllViews()container.addView(surfaceView)// 将 SurfaceView 对象传入声网实时互动 SDK,设置本地视图this.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_HIDDEN, 0))}
}

获取token,并且处理token过期问题

 private val mRtcEventHandler: IRtcEngineEventHandler = object : IRtcEngineEventHandler() {//token监听override fun onTokenPrivilegeWillExpire(token: String?) {super.onTokenPrivilegeWillExpire(token)needUpdateToken = truemPresenter.getLiveToken()}override fun onRequestToken() {super.onRequestToken()needUpdateToken = truemPresenter.getLiveToken()}
}
//处理token过期问题
if (needUpdateToken) {mRtcEngine?.renewToken(mPresenter.roomToken?.token.orEmpty())return
}

获取直播间配置参数

其中,不同分辨率和帧率下适配的码率可以看这个文档:

//获取直播间配置参数
mRtcEngine?.queryDeviceScore()?.let {mPresenter.getLiveConfig(it)
}
override fun getLiveConfig() {mPresenter.liveRoomConfig?.definition_high?.let {setVideoEncoderConfiguration(it)}
}
private fun setVideoEncoderConfiguration(configuration: LiveRoomDefinitionMedium) {if (renderMode != configuration.render_mode) {mRtcEngine?.setupLocalVideo(VideoCanvas(surfaceView, configuration.render_mode, 0))renderMode = configuration.render_mode}videoEncoderConfiguration.bitrate = configuration.bitratevideoEncoderConfiguration.frameRate = configuration.frame_rate //帧率videoEncoderConfiguration.mirrorMode = configuration.getMirrorMode()videoEncoderConfiguration.dimensions = configuration.getDimensions() //分辨率videoEncoderConfiguration.orientationMode = configuration.getOrientationMode()//自适应模式val res = mRtcEngine?.setVideoEncoderConfiguration(videoEncoderConfiguration)
}

加入频道并发布音视频流

如果是要为极速直播,则需要多设置:将 options 参数设置为 AUDIENCE_LATENCY_LEVEL_LOW_LATENCY(低延时)。

//加入频道并发布音视频流
private fun initChannelMediaOptions() {mPresenter.roomToken?.let {// 创建 ChannelMediaOptions 对象,并进行配置val options = ChannelMediaOptions()options.clientRoleType =  if (isFromControl) Constants.CLIENT_ROLE_AUDIENCE else Constants.CLIENT_ROLE_BROADCASTERoptions.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTINGval res = mRtcEngine?.joinChannel(it.token,liveRoomBean.room_id,if (isFromControl) it.audience_uid.toIntDefault else it.live_uid.toIntDefault,options)if(res == 0){isConnectionLost = falseif(isFromControl.not()){mPresenter.startPushStream()}}} ?: run {toast("直播地址为空!!")}
}

观看端

如果是设置观看端的话,则可以使用setupRemoteVideo。

离开页面的时候,需要留意关闭预览

 mRtcEngine?.stopPreview()
mRtcEngine?.leaveChannel()

一些功能使用和踩坑记录

处理截图问题

需要留意的是,不能直接在takeSnapshot这里拿到路径就开始操作。而应该到回调onSnapshotTaken中处理。
处理的过程需要留意耗时的操作要放到子线程去执行。

 private fun takeSnapshot(fileName: String = "${System.currentTimeMillis()}.jpg") {val uid = if(isFromControl) mPresenter.roomToken?.live_uid.toIntDefault else 0val filePath: String = SaveUtils.mkdir("live") +  File.separator + fileNameval ret: Int? = mRtcEngine?.takeSnapshot(uid, filePath)
}

tip:这里遇到一个声网的bug,在使用远端用户进行截图的时候,能够正常返回图片资源。但是如果使用主播端,则会发现有延迟问题。
通过排查定位,发现这种延迟不是时间上的。而是会返回上一次截图的资源过来。目前已同步给声网,声网表示已跟进这个bug。
但是,我们还需要解决。所以首先,这边是考虑自己实现截图。简易代码如下:

 fun takeScreen(view: View,path: String?,){val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)val canvas = Canvas(bitmap)view.draw(canvas)saveBitmap(bitmap, path)}private fun saveBitmap(bitmap: Bitmap,path: String? = null,){try {val imageFile = File(path)val fos = FileOutputStream(imageFile)bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)fos.flush()fos.close()} catch (e: IOException) {e.printStackTrace()}}

通过验证,SurfaceView这样子截图是会失败的,截取到数据是0size。于是改成传入其包裹的容器之后,验证发现SurfaceView部分是黑屏。
当然也不能直接截全屏,因为不符合业务需求。声网官方示例中是通过SurfaceView来作为视图组件的。(后面通过验证,使用TextureView替代SurfaceView也是可以。)
而SurfaceView 是用于绘制图形的视图组件,它通常不会保存绘制的内容,而是直接将内容显示在屏幕上。具体来说,SurfaceView 的绘制是由 SurfaceFlinger 系统服务管理的,
它将 SurfaceView 的内容绘制到屏幕上的一个独立的 Surface 上。这个过程是在底层硬件加速的情况下进行的,绘制的内容并不会保存在普通的 Bitmap 中,
因此无法直接通过传统的方法获取 SurfaceView 的截图。这边不过多对SurfaceView做详解。

SurfaceView采用双缓存机制,SurfaceView在更新视图时用到了两张 Canvas,一张 frontCanvas 和一张 backCanvas ,每次实际显示的是 frontCanvas ,backCanvas 存储的是上一次更改前的视图。当你在播放这一帧的时候,它已经提前帮你加载好后面一帧了,所以播放起视频很流畅。当使用lockCanvas() 获取画布时,得到的实际上是backCanvas 而不是正在显示的 frontCanvas ,之后你在获取到的 backCanvas 上绘制新视图,
再 unlockCanvasAndPost(canvas)此视图,那么上传的这张 canvas 将替换原来的 frontCanvas 作为新的frontCanvas ,原来的 frontCanvas 将切换到后台作为 backCanvas 。例如,如果你已经先后两次绘制了视图A和B,那么你再调用 lockCanvas() 获取视图,获得的将是A而不是正在显示的B,之后你将重绘的 A 视图上传,那么 A 将取代 B 作为新的 frontCanvas 显示在SurfaceView 上,原来的B则转换为backCanvas。相当与多个线程,交替解析和渲染每一帧视频数据
引用 https://www.jianshu.com/p/a2a235bee59e
普通View onDraw 内容是静态的,不调invalidate() 它是不会发生变化,你可以拿到里面的Bitmap;但是SurfaceView不同,无法拿到它back buffer里面的Bitmap。

回到解决问题本身,既然没办法直接通过对SurfaceView截图。还是从声网的api入手。最后验证通过截图两次取第二次的方式可以解决该问题。
但是还需要注意的是,不能简单粗暴直接调用两次api。否则api还是返回异常。而是在调用第一次之后,在onSnapshotTaken再判断处理调起第二次。

设置清晰度

在设置清晰度的时候,需要先得知当前设备的设备评分等级。使用的api是:queryDeviceScore。然后再根据分数,得到适合配置进行设置。
在高清或超高清视频场景下,可以先调用该方法查询设备的等级评分。如果返回的评分较低(比如低于 60),则需要适当调低视频分辨率,以避免影响视频体验。

 private fun setVideoEncoderConfiguration(configuration: LiveRoomDefinitionMedium) {videoEncoderConfiguration.bitrate = configuration.bitratevideoEncoderConfiguration.frameRate = configuration.frame_rate //帧率videoEncoderConfiguration.mirrorMode = configuration.getMirrorMode()videoEncoderConfiguration.dimensions = configuration.getDimensions() //分辨率videoEncoderConfiguration.orientationMode = configuration.getOrientationMode()//自适应模式val res = mRtcEngine?.setVideoEncoderConfiguration(videoEncoderConfiguration)
}

token失效问题

不仅在加入频道的时候需要token,直播过程也会一直监测token的时效。因此还需要监听。声网提供了两个api:

  • onTokenPrivilegeWillExpire
  • onRequestToken。
    需要在监听到token过期或即将过期的时候,重新拿到新的token,并通过renewToken重新赋值。

对焦问题

声网提供了相关对焦的功能,包括人脸自动对焦和手动对焦功能。

  • isCameraFocusSupported 检测设备是否支持手动对焦功能
  • isCameraAutoFocusFaceModeSupported 检测设备是否支持人脸对焦功能
  • setCameraFocusPositionInPreview 设置手动对焦位置,并触发对焦
  • setCameraAutoFocusFaceModeEnabled 设置是否开启人脸对焦功能
    经过验证,发现人脸对焦在前置摄像的时候会检测不支持。因此联系声网,得到反馈是就算开启了人脸对焦,也会比较损耗性能。
    再加上经过多次验证发现,再切换镜头的时候立即调用也偶现失败。结合业务大多是开启前置,因此只接入手动对焦功能。
    而手动对焦功能,发现相比腾讯的手动对焦,声网的手动对焦感官体验上只会晃动一下,因此最好还是像腾讯的一样加多个动效。
    目前初步实现如下,感兴趣可以看看,也可以直接跳过这趴:
surfaceView.setOnTouchListener { view, event ->when (event.action) {MotionEvent.ACTION_DOWN -> {val x = event.xval y = event.y// 在这里处理点击事件,可以使用 x 和 y 坐标执行相应操作// 检测当前设备是否支持手动对焦并设置。if (isCameraFocusSupported) {// 假设在屏幕(50,100)的位置对焦。val res1 =  setCameraFocusPositionInPreview(x, y)if(res1 == 0){val borderAnimationView = BorderAnimationView(this@LivePlayerActivityV2)local_video_view_container.addView(borderAnimationView)borderAnimationView.showAnimation(x, y)Handler(Looper.getMainLooper()).postDelayed({local_video_view_container.removeView(borderAnimationView)}, 500) //850毫秒后执行}}true}else -> false}}

BorderAnimationView则是一个直播对焦边框动画View,这边处理的方式:通过绘制的方式画出对应区域内的一个白色边框。然后开启缩放动画效果,最后消失。
不多说,初步代码如下:

class BorderAnimationView(context: Context) : View(context) {private var x = 0fprivate var y = 0fprivate var scale = 1fprivate var paint = Paint().apply {color = Color.WHITEstyle = Paint.Style.STROKEstrokeWidth = 3f}fun showAnimation(x: Float, y: Float) {this.x = xthis.y = yscale = 1finvalidate() // 请求重绘startScaleAnimation()}private fun startScaleAnimation() {val scaleTo = 1.25fval scaleBack = 1fval scaleAnimation = ValueAnimator.ofFloat(scale, scaleTo)scaleAnimation.addUpdateListener { valueAnimator ->scale = valueAnimator.animatedValue as Floatinvalidate()}scaleAnimation.duration = 250 // 0.25秒scaleAnimation.interpolator = AccelerateDecelerateInterpolator()val scaleBackAnimation = ValueAnimator.ofFloat(scaleTo, scaleBack)scaleBackAnimation.addUpdateListener { valueAnimator ->scale = valueAnimator.animatedValue as Floatinvalidate()}scaleBackAnimation.duration = 350 // 0.35秒scaleBackAnimation.interpolator = AccelerateDecelerateInterpolator()scaleAnimation.addListener(object : AnimatorListenerAdapter() {override fun onAnimationEnd(animation: Animator?) {scaleBackAnimation.start()}})scaleAnimation.start()}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)val scaledSize = 80.dp * scaleval halfScaledSize = scaledSize / 2canvas.drawRect(x - halfScaledSize, y - halfScaledSize, x + halfScaledSize, y + halfScaledSize, paint) // 绘制带有白色边框的矩形}
}

网络监听

直播过程避免网络波动,因此需要监听网络做出必要的交互。声网提供的api是onNetworkQuality。
需要注意的是,要判断是主播端还是观看端。
如果是主播端,则应该拿上行网络质量txQuality进行判断;如果是观看端,则应该用下行网络质量rxQuality。
不足的地方是,只能拿到网络质量状态的枚举,而没能拿到具体网络速率等数值。

其他

  • 美颜美白:这个主要是业务逻辑比较多,实际核心代码就是调用setBeautyEffectOptions进行设置。声网还支持了更多的美颜面板设置。
mRtcEngine?.setBeautyEffectOptions(true, options)
  • 告警通知服务:比如视频卡顿率在一定周期内连续大于某个阈值。告警通知

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

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

相关文章

别再写传统简历了!AI简历5个超实用的功能,助你求职一臂之力(强烈建议收藏)

你们在制作简历时,是不是基本只关注两件事:简历模板,还有基本信息的填写。 当你再次坐下来更新你的简历时,可能会发现自己不自觉地选择了那个“看起来最好看的模板”,填写基本信息,却没有深入思考如何使简历更具吸引力。这其实是一个普遍现象:许多求职者仍停留在传统简历…

瑞萨:推迟加薪并裁员 | 百能云芯

随着全球半导体市场进入缓慢复苏阶段&#xff0c;日本汽车和工业芯片巨头瑞萨电子近期宣布了一系列重要的经营决策。据外媒报道&#xff0c;瑞萨电子已决定推迟今年4月至10月的定期加薪&#xff0c;并在自2023年11月以来进行了有限规模的裁员&#xff0c;以应对市场的变化和压力…

LC3014 输入单词需要的最少按键次数Ⅰ与方法内容的易读性

题目 刷题做到力扣 3014&#xff0c;题目要求设计电话键盘上的按键映射&#xff0c;返回按出 word 单词的最小按键次数&#xff0c;1 ≤ word.length ≤ 26&#xff0c;且仅由小写英文字母组成&#xff0c;所有字母互不相同 我的题解 简单题&#xff0c;略加思索拿下&#x…

代码随想录算法训练营第36天—动态规划04 | ● 背包问题 ● 01背包 (二维数组解法和滚动数组解法) ● *416. 分割等和子集

背包问题 常见的背包问题类型&#xff08;大厂面试重点掌握01背包和完全背包即可&#xff09;题目描述&#xff1a;有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品能用*次&#xff0c;求解怎么装物品使得装入…

识别恶意IP地址的有效方法

在互联网的环境中&#xff0c;恶意IP地址可能会对网络安全造成严重威胁&#xff0c;例如发起网络攻击、传播恶意软件等。因此&#xff0c;识别恶意IP地址是保护网络安全的重要一环。IP数据云将探讨一些有效的方法来识别恶意IP地址。 IP地址查询&#xff1a;https://www.ipdata…

面试几个问题总结

如何保证MQ高可用 RabbitMQ 高可用性保证: 镜像队列(Mirrored Queues) 镜像集群模式:这是RabbitMQ中最常用的实现高可用的方式。在一个镜像队列中,同一队列会被复制到多个节点上,形成一个镜像队列集合。当其中一个节点故障时,其他拥有相同队列镜像的节点可以继续提供服…

代码随想录算法训练营第七天| 344.反转字符串,541.反转字符串Ⅱ,卡码网:54.替换数字,151.翻转字符串里的单词,卡码网:55.右旋转字符串

344.反转字符串 文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;字符串基础操作&#xff01; | LeetCode&#xff1a;344.反转字符串_哔哩哔哩_bilibili 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 无 时间复杂度: O(n)空间复…

S5PV210_视频编解码项目_裸机开发:实现按键的外部中断处理

加粗样式本文所作内容&#xff1a; 基于S5PV210芯片实现按键的外部中断处理程序&#xff0c;搭建中断处理流程框架 S5PV210对于中断处理的操作流程 1 外部中断得到触发&#xff1a; 1&#xff09;外部中断在初始化阶段得到使能 2&#xff09;外界达到了外部中断的触发条件 …

汉诺塔问题代码写法的详细解析

汉诺塔游戏规则&#xff1a; 规则&#xff1a; 汉诺塔问题是一个经典的问题。汉诺塔&#xff08;Hanoi Tower&#xff09;&#xff0c;又称河内塔&#xff0c;源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子&#xff0c;在一根柱子上从下往上按照大小顺序摞着…

有来团队后台项目-解析3

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、引入Elment-plus安装element-plus安装icons修改tsconfig.json (可选)配置按需自动引入验证是否导入成功展示效果typings 文件夹里生成两个文件总结一、引入Elment-plus elementplus 安装el…

30天学会QT(进阶)--------------第二天(创建项目)

1、如何规范的创建一个项目 由于本人也是从其他的项目上学来的&#xff0c;所以也不算是业界规范&#xff0c;每个公司或者个人都有自己的方式去创建项目&#xff0c;项目的创建是本着简洁&#xff0c;明了&#xff0c;方便而言的&#xff0c;所以对于我来说&#xff0c;不繁琐…

案例--某站视频爬取

众所周知&#xff0c;某站的视频是&#xff1a; 由视频和音频分开的。 所以我们进行获取&#xff0c;需要分别获得它的音频和视频数据&#xff0c;然后进行音视频合并。 这么多年了&#xff0c;某站还是老样子&#xff0c;只要加个防盗链就能绕过。&#xff08;防止403&#xf…

蓝桥杯刷题(五)

[蓝桥杯 2022 省 B] 刷题统计 题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示 题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a a…

RocketMQ学习笔记三(面试题)

【RocketMQ面试题&#xff08;23道&#xff09;】-CSDN博客 ------------------------------------------------------------------------------------------------ 最好的学习资源在官网&#xff1a;初识RocketMQ | RocketMQ (apache.org) 以下内容来源于官网哦。 基本概念…

fs模块 文件写入 之 追加写入

文件的同步、异步追加写入&#xff1a; 一、异步追加 &#xff08;1&#xff09;语法&#xff1a;fs.appendFile(path,data,[options],callback(data,err)) &#xff08;2&#xff09;操作 1》引入fs模块 const fsrequire(fs); 2》调用appendFile fs.appendFile(./我可以…

大数据开发(HBase面试真题-卷一)

大数据开发&#xff08;HBase面试真题&#xff09; 1、请解释Hive和HBase之间的主要区别&#xff1f;2、描述一下Apache HBase与关系数据库之间有何区别&#xff1f;3、简要介绍HDFS和HBase&#xff0c;并描述它们适用的场景。4、HBase Column Family的概念是什么&#xff1f;5…

如何发现并防范“隐蔽式”CC攻击

网络安全是当今互联网时代不可忽视的重要议题。随着科技的发展&#xff0c;黑客渗透技术也日益复杂和潜在危险。德迅云安全为用户提供全方位的保护&#xff0c;确保用户信息安全 http&https是什么&#xff1f; HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超…

学python新手如何安装pycharm;python小白如何安装pycharm

首先找到官网&#xff1a; Download PyCharm: The Python IDE for data science and web development by JetBrains 打开后选择下载&#xff0c;下图标红部分 点击exe程序&#xff0c;点击下一步&#xff01; 选择安装路径&#xff0c;下一步 弹出界面全选 选择默认 然后直接…

前端面试练习24.3.13

1.请描述下对vue生命周期的理解 Vue 组件的生命周期是指组件从创建、挂载到销毁的整个过程中所经历的一系列钩子函数的调用顺序。 在vue3 中&#xff0c;我们使用了组合式的API&#xff0c;使用了setup语法糖&#xff0c;提供了更灵活的方式来组织组件的逻辑&#xff0c;不再依…

Java基于微信小程序的校园生活互助小助手

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…