在 Flutter 多人视频中实现虚拟背景、美颜与空间音效
前言
在之前的「基于声网 Flutter SDK 实现多人视频通话」里,我们通过 Flutter + 声网 SDK 完美实现了跨平台和多人视频通话的效果,那么本篇我们将在之前例子的基础上进阶介绍一些常用的特效功能,包括虚拟背景、色彩增强、空间音频、基础变声功能。
本篇主要带你了解 SDK 里几个实用的 API 实现,相对简单。
01 虚拟背景
虚拟背景是视频会议里最常见的特效之一,在声网 SDK 里可以通过enableVirtualBackground
方法启动虚拟背景支持。(点击这里查看虚拟背景接口文档)。
首先,因为我们是在 Flutter 里使用,所以我们可以在 Flutter 里放一张assets/bg.jpg
图片作为背景,这里有两个需要注意的点:
assets/bg.jpg
图片需要在pubspec.yaml
文件下的assets
添加引用
assets: - assets/bg.jpg
- 需要在
pubspec.yaml
文件下添加path_provider: ^2.0.8
和path: ^1.8.2
依赖,因为我们需要把图片保存在 App 本地路径下
如下代码所示,首先我们通过 Flutter 内的rootBundle
读取到bg.jpg
,然后将其转化为bytes
, 之后调用getApplicationDocumentsDirectory
获取路径,保存在的应用的/data"
目录下,然后就可以把图片路径配置给enableVirtualBackground
方法的source
,从而加载虚拟背景。
Future<void> _enableVirtualBackground() async { ByteData data = await rootBundle.load("assets/bg.jpg"); List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); Directory appDocDir = await getApplicationDocumentsDirectory(); String p = path.join(appDocDir.path, 'bg.jpg'); final file = File(p); if (!(await file.exists())) { await file.create(); await file.writeAsBytes(bytes); } await _engine.enableVirtualBackground( enabled: true, backgroundSource: VirtualBackgroundSource( backgroundSourceType: BackgroundSourceType.backgroundImg, source: p), segproperty: const SegmentationProperty(modelType: SegModelType.segModelAi)); setState(() {}); }
如下图所示是都开启虚拟背景图片之后的运行效果,当然,这里还有两个需要注意的参数:
BackgroundSourceType
:可以配置backgroundColor
(虚拟背景颜色)、backgroundImg
(虚拟背景图片)、backgroundBlur
(虚拟背景模糊) 这三种情况,基本可以覆盖视频会议里的所有场景SegModelType
:可以配置为segModelAi
(智能算法)或segModelGreen
(绿幕算法)两种不同场景下的抠图算法。
这里需要注意的是,在官方的提示里,建议只在搭载如下芯片的设备上使用该功能(应该是对于 GPU 有要求):
- 骁龙 700 系列 750G 及以上
- 骁龙 800 系列 835 及以上
- 天玑 700 系列 720 及以上
- 麒麟 800 系列 810 及以上
- 麒麟 900 系列 980 及以上
另外需要注意的是,为了将自定义背景图的分辨率与 SDK 的视频采集分辨率适配,声网 SDK 会在保证自定义背景图不变形的前提下,对自定义背景图进行缩放和裁剪。
02 美颜
美颜作为视频会议里另外一个最常用的功能,声网也提供了setBeautyEffectOptions
方法支持一些基础美颜效果调整。(点击查看美颜接口文档)。
如下代码所示,setBeautyEffectOptions
方法里主要是通过BeautyOptions
来调整画面的美颜风格,参数的具体作用如下表格所示。
这里的 .5 只是做了一个 Demo 效果,具体可以根据你的产品需求,配置出几种固定模版让用户选择。
_engine.setBeautyEffectOptions( enabled: true, options: const BeautyOptions( lighteningContrastLevel: LighteningContrastLevel.lighteningContrastHigh, lighteningLevel: .5, smoothnessLevel: .5, rednessLevel: .5, sharpnessLevel: .5, ), );
运行后效果如下图所示,开了 0.5 参数后的美颜整体画面更加白皙,同时唇色也更加明显。
没开美颜 | 开了美颜 |
---|---|
03 色彩增强
接下来要介绍的一个 API 是色彩增强:setColorEnhanceOptions
,如果是美颜还无法满足你的需求,那么色彩增强 API 可以提供更多参数来调整你的需要的画面风格。(点击查看色彩增强接口文档)
如下代码所示,色彩增强 API 很简单,主要是调整ColorEnhanceOptions
的 strengthLeve
l和skinProtectLevel
参数,也就是调整色彩强度和肤色保护的效果。
_engine.setColorEnhanceOptions( enabled: true, options: const ColorEnhanceOptions( strengthLevel: 6.0, skinProtectLevel: 0.7));
如下图所示,因为摄像头采集到的视频画面可能存在色彩失真的情况,而色彩增强功能可以通过智能调节饱和度和对比度等视频特性,提升视频色彩丰富度和色彩还原度,最终使视频画面更生动。
开启增强之后画面更抢眼了。
没开增强 | 开了美颜+增强 |
---|---|
04 空间音效
其实声音调教才是重头戏,声网既然叫声网,在音频处理上肯定不能落后,在声网 SDK 里就可以通过enableSpatialAudio
打开空间音效的效果。(点击查看空间音效接口文档)
_engine.enableSpatialAudio(true);
什么是空间音效?简单说就是特殊的 3D 音效,它可以将音源虚拟成从三维空间特定位置发出,包括听者水平面的前后左右,以及垂直方向的上方或下方。
本质上空间音效就是通过一些声学相关算法计算,模拟实现类似空间 3D 效果的音效实现。
同时你还可以通过setRemoteUserSpatialAudioParams
来配置空间音效的相关参数,如下表格所示,可以看到声网提供了非常丰富的参数来让我们可以自主调整空间音效,例如这里面的enable_blur
和enable_air_absorb
效果就很有意思,十分推荐大家去试试。
音频类的效果这里就无法展示了,强烈推荐大家自己动手去试试。
05 人声音效
另外一个推荐的 API 就是人声音效:setAudioEffectPreset
, 调用该方法可以通过 SDK 预设的人声音效,(点击查看人声音效接口文档)在不会改变原声的性别特征的前提下,修改用户的人声效果,例如:
_engine.setAudioEffectPreset(AudioEffectPreset.roomAcousticsKtv);
声网 SDK 里预设了非常丰富的AudioEffectPreset
,如下表格所示,从场景效果如 KTV、录音棚,到男女变声,再到恶搞的音效猪八戒等,可以说是相当惊艳。
PS:为获取更好的人声效果,需要在调用该方法前将setAudioProfile的 scenario
设为audioScenarioGameStreaming(3):
_engine.setAudioProfile( profile: AudioProfileType.audioProfileDefault, scenario: AudioScenarioType.audioScenarioGameStreaming);
当然,这里需要注意的是,这个方法只推荐用在对人声的处理上,不建议用于处理含音乐的音频数据。
最后,完整代码如下所示:
class VideoChatPage extends StatefulWidget { const VideoChatPage({Key? key}) : super(key: key); @override State<VideoChatPage> createState() => _VideoChatPageState(); } class _VideoChatPageState extends State<VideoChatPage> { late final RtcEngine _engine; ///初始化状态 late final Future<bool?> initStatus; ///当前 controller late VideoViewController currentController; ///是否加入聊天 bool isJoined = false; /// 记录加入的用户id Map<int, VideoViewController> remoteControllers = {}; @override void initState() { super.initState(); initStatus = _requestPermissionIfNeed().then((value) async { await _initEngine(); ///构建当前用户 currentController currentController = VideoViewController( rtcEngine: _engine, canvas: const VideoCanvas(uid: 0), ); return true; }).whenComplete(() => setState(() {})); } Future<void> _requestPermissionIfNeed() async { if (Platform.isMacOS) { return; } await [Permission.microphone, Permission.camera].request(); } Future<void> _initEngine() async { //创建 RtcEngine _engine = createAgoraRtcEngine(); // 初始化 RtcEngine await _engine.initialize(const RtcEngineContext( appId: appId, )); _engine.registerEventHandler(RtcEngineEventHandler( // 遇到错误 onError: (ErrorCodeType err, String msg) { if (kDebugMode) { print('[onError] err: $err, msg: $msg'); } }, onJoinChannelSuccess: (RtcConnection connection, int elapsed) { // 加入频道成功 setState(() { isJoined = true; }); }, onUserJoined: (RtcConnection connection, int rUid, int elapsed) { // 有用户加入 setState(() { remoteControllers[rUid] = VideoViewController.remote( rtcEngine: _engine, canvas: VideoCanvas(uid: rUid), connection: const RtcConnection(channelId: cid), ); }); }, onUserOffline: (RtcConnection connection, int rUid, UserOfflineReasonType reason) { // 有用户离线 setState(() { remoteControllers.remove(rUid); }); }, onLeaveChannel: (RtcConnection connection, RtcStats stats) { // 离开频道 setState(() { isJoined = false; remoteControllers.clear(); }); }, )); // 打开视频模块支持 await _engine.enableVideo(); // 配置视频编码器,编码视频的尺寸(像素),帧率 await _engine.setVideoEncoderConfiguration( const VideoEncoderConfiguration( dimensions: VideoDimensions(width: 640, height: 360), frameRate: 15, ), ); await _engine.startPreview(); } @override void dispose() { _engine.leaveChannel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Stack( children: [ FutureBuilder<bool?>( future: initStatus, builder: (context, snap) { if (snap.data != true) { return const Center( child: Text( "初始化ing", style: TextStyle(fontSize: 30), ), ); } return AgoraVideoView( controller: currentController, ); }), Align( alignment: Alignment.topLeft, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( ///增加点击切换 children: List.of(remoteControllers.entries.map( (e) => InkWell( onTap: () { setState(() { remoteControllers[e.key] = currentController; currentController = e.value; }); }, child: SizedBox( width: 120, height: 120, child: AgoraVideoView( controller: e.value, ), ), ), )), ), ), ) ], ), floatingActionButton: FloatingActionButton( onPressed: () async { // 加入频道 _engine.joinChannel( token: token, channelId: cid, uid: 0, options: const ChannelMediaOptions( channelProfile: ChannelProfileType.channelProfileLiveBroadcasting, clientRoleType: ClientRoleType.clientRoleBroadcaster, ), ); }, ), persistentFooterButtons: [ ElevatedButton.icon( onPressed: () { _enableVirtualBackground(); }, icon: const Icon(Icons.accessibility_rounded), label: const Text("虚拟背景")), ElevatedButton.icon( onPressed: () { _engine.setBeautyEffectOptions( enabled: true, options: const BeautyOptions( lighteningContrastLevel: LighteningContrastLevel.lighteningContrastHigh, lighteningLevel: .5, smoothnessLevel: .5, rednessLevel: .5, sharpnessLevel: .5, ), ); //_engine.setRemoteUserSpatialAudioParams(); }, icon: const Icon(Icons.face), label: const Text("美颜")), ElevatedButton.icon( onPressed: () { _engine.setColorEnhanceOptions( enabled: true, options: const ColorEnhanceOptions( strengthLevel: 6.0, skinProtectLevel: 0.7)); }, icon: const Icon(Icons.color_lens), label: const Text("增强色彩")), ElevatedButton.icon( onPressed: () { _engine.enableSpatialAudio(true); }, icon: const Icon(Icons.surround_sound), label: const Text("空间音效")), ElevatedButton.icon( onPressed: () { _engine.setAudioProfile( profile: AudioProfileType.audioProfileDefault, scenario: AudioScenarioType.audioScenarioGameStreaming); _engine .setAudioEffectPreset(AudioEffectPreset.roomAcousticsKtv); }, icon: const Icon(Icons.surround_sound), label: const Text("人声音效")), ]); } Future<void> _enableVirtualBackground() async { ByteData data = await rootBundle.load("assets/bg.jpg"); List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); Directory appDocDir = await getApplicationDocumentsDirectory(); String p = path.join(appDocDir.path, 'bg.jpg'); final file = File(p); if (!(await file.exists())) { await file.create(); await file.writeAsBytes(bytes); } await _engine.enableVirtualBackground( enabled: true, backgroundSource: VirtualBackgroundSource( backgroundSourceType: BackgroundSourceType.backgroundImg, source: p), segproperty: const SegmentationProperty(modelType: SegModelType.segModelAi)); setState(() {}); } }
06 最后
本篇的内容作为「基于声网 Flutter SDK 实现多人视频通话」的补充,相对来说内容还是比较简单,不过可以看到不管是在画面处理还是在声音处理上,声网 SDK 都提供了非常便捷的 API 实现,特别在声音处理上,因为文章限制这里只展示了简单的 API 介绍,所以强烈建议大家自己尝试下这些音频 API ,真的非常有趣。除此之外,还有许多场景与玩法,可以点击此处访问官网了解。
欢迎开发者们也尝试体验声网 SDK,实现实时音视频互动场景。现注册声网账号下载 SDK,可获得每月免费 10000 分钟使用额度。如在开发过程中遇到疑问,可在声网开发者社区与官方工程师交流。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
微前端框架single-spa子应用加载解析
作者:京东物流 宁冲 1 前言 什么是微前端? 微前端是指存在于浏览器中的微服务。 本文主要通过对微前端框架single-spa的基座应用加载子应用的single-spa-vue函数库进行分析,通过代码维度分析让大家了解在single-spa加载子应用的时候都做了哪些事情。如何通过优化single-spa-vue函数库保持子应用的状态。 由于是在代码维度进行分析,要求读者对single-spa有一定的了解,阅读效果会更好。 2 single-spa加载子应用的过程 基座应用中配置的加载子应用配置,在配置子应用对象的app方法中会把子应用chunk-vendors.js、app.js插入到页面,并且执行chunk-vendors.js、app.js两个子应用依赖的js。 app方法执行结束以后,子应用依赖的js插入页面以后的dom结构。 由上面app方法的执行过程,我们可以看出基座应用加载子应用,和vue框架打包后加载js资源的方式类似。那么为什么只需要加载子应用的app.js和chunk-vendors.js就可以把子应用渲染到页面上面。下面我们再来看看子应用的入口文件main.js方...
- 下一篇
cookie时效无限延长方案
作者:京东科技 刘清洁 1、痛点(*) 自动化测试有2种形式,接口自动化和UI自动化。而UI自动化经常会被登录节点堵塞,例如验证码、图形、滑块等,尽管有些方式可以识别图形和定位滑块位置,但成功率都不高,无法真正意义上实现自动化执行;而http接口的自动化测试前置如果依赖cookie,也无法实现自动化执行。 a、怎么样才能绕过登录,实现从前端到后端的自动化执行 b、面对复杂的登录验证无法直接自动获取到cookie,需要人工操作登录,而cookie又有时效,不能长久使用 本方案将有效解决以上问题,在面对复杂的登录验证及有cookie时效的模式下,可以将短暂时效的cookie改为长久有效,真正意义上实现UI自动化和依赖cookie鉴权的接口自动化。 2、什么是cookie cookie称之为会话跟踪技术,是一个很小的文本文件,是浏览器储存在用户的机器上的。Cookie是纯文本,没有可执行代码。储存一些服务器需要的信息,每次请求站点,会发送相应的cookie,这些cookie可以用来辨别用户身份信息等作用 3、过期时间查看方式 打开浏览器,并转到您希望查看 cookie 的网站。 按 F12 ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Red5直播服务器,属于Java语言的直播服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7