在 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);@overrideState<VideoChatPage> createState() => _VideoChatPageState();
}class _VideoChatPageState extends State<VideoChatPage> {late final RtcEngine _engine;///初始化状态late final Future<bool?> initStatus;///当前 controllerlate VideoViewController currentController;///是否加入聊天bool isJoined = false;/// 记录加入的用户idMap<int, VideoViewController> remoteControllers = {};@overridevoid initState() {super.initState();initStatus = _requestPermissionIfNeed().then((value) async {await _initEngine();///构建当前用户 currentControllercurrentController = 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();// 初始化 RtcEngineawait _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();}@overridevoid dispose() {_engine.leaveChannel();super.dispose();}@overrideWidget 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 分钟使用额度。如在开发过程中遇到疑问,可在声网开发者社区与官方工程师交流。
在 Flutter 多人视频通话中实现虚拟背景、美颜与空间音效相关推荐
- 基于声网 Flutter SDK 实现多人视频通话
前言 本文是由声网社区的开发者"小猿"撰写的Flutter基础教程系列中的第一篇.本文除了讲述实现多人视频通话的过程,还有一些 Flutter 开发方面的知识点.该系列将基于声网 ...
- Android技术分享| 【Android 自定义View】多人视频通话控件
[Android 自定义View]多人视频通话控件 *以上图片截自微信等待中界面 等待中界面 上图是微信多人视频通话时未接通的界面状态,可见每个人的 View 中大致需包含了以下元素. 头像 昵称 L ...
- 如何在 Web 实现支持虚拟背景的视频会议
前言 众所周知,市面上有比如飞书会议.腾讯会议等实现视频会议功能的应用,而且随着这几年大环境的影响,远程协作办公越来越成为常态,关于视频会议的应用也会越来越多,且在远程办公的沟通协作中对沟通软件的使用 ...
- 用Flutter构建一个 视频通话联系人管理应用
用Flutter构建一个 视频 / 通话 / 联系人管理 应用 这是编程务实的第三个实验, 要完成一个简单的拨号器.在此需求基础上,我用Flutter添加了一些全新的功能,包括视频通话,观众视角通话, ...
- IT-标准化-系列-15.在VPC 2007中构建虚拟网络
看过太多人使用VPC 看过太多人使用Microsoft (R) Virtual Server 2005 R2 看过太多人使用VMware Workstation 没见过有人你像我这样夸张过! 为构建虚 ...
- 商务网站建设中的虚拟主机技术
电子商务是高速发展的商务运作模式,是未来商务交易的主要运营方式.电子商务的实施与运作依赖于电子商务系统的构建,电子商务网站则是电子商务系统工作和运行的主要载体,是企业在互联网上的"虚拟公司& ...
- 直播软件源码如何在Android端实现多人视频通话
本篇主要讨论直播软件源码如何在Android端实现多人视频通话.主要需要实现以下功能: 随着加入人数和他们的手机摄像头分辨率的变化,显示不同的UI,即所谓的"分屏" 点击分屏中的小 ...
- Android端实现多人音视频聊天应用(二):多人视频通话
作者:声网用户,资深Android工程师吴东洋 本系列文章分享了基于Agora SDK 2.1实现多人视频通话的实践经验. 在上一篇<Android 多人视频聊天应用的开发(一)一对一聊天> ...
- webRtc+websocket多人视频通话
webRTc+ websocket实现多人视频通话,目前此demo只支持crome浏览器, 版本仅仅支持:ChromeStandalone_46.0.2490.80_Setup.1445829883 ...
最新文章
- 活动图与流程图的区别
- Hexo搭建个人博客常用命令
- vscode安装和使用
- 理论基础 —— 索引 —— 分块索引
- thinking of 抵制家乐福
- hypermesh安装包百度云_HYPERMESH11.0 64bt软件
- 新春福利 | CSDN 祝所有程序员朋友新年快乐,万事顺心!
- 《易扫码》APP技术服务支持与隐私政策
- 科技四巨头展望:战线***升级 软硬界限模糊
- WolframTones:用一种新科学谱写一种新音乐
- c语言可不可以不要.cpp文件,[求助]一个头文件和CPP文件的问题
- 出版了两本实体书,写了9本开源电子书,还上了百度百科,冰河这是要起飞了吗?(建议收藏)
- php编辑日历,【PHP】制作日历
- 教育技术学就业方向_教育技术学专业就业方向与就业前景
- 安卓数据传递和数据回传简单注册和宠物装备购买
- windows10下安装Box2D
- 认识计算机综合题,第一章 认识计算机试题.doc
- 解决:Error: geom_point requires the following missing aesthetics: y Run `rlang::last_error()`
- 原来工业互联网和工业物联网是两个东西啊
- 作为软件开发人员,3种简单的方法来推销自己