在之前的「基于声网 Flutter SDK 实现多人视频通话」里,我们通过 Flutter + 声网 SDK 完美实现了跨平台和多人视频通话的效果,那么本篇我们将在之前例子的基础上进阶介绍一些常用的特效功能。
虚拟背景是视频会议里最常见的特效之一,在声网 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 及以上
美颜作为视频会议里另外一个最常用的功能,声网也提供了setBeautyEffectOptions方法支持一些基础美颜效果调整。
如下代码所示,setBeautyEffectOptions方法里主要是通过BeautyOptions来调整画面的美颜风格,参数的具体作用如下表格所示。
这里的 .5 只是做了一个 Demo 效果,具体可以根据你的产品需求,配置出几种固定模版让用户选择。
_engine.setBeautyEffectOptions(enabled: true,options: const BeautyOptions(lighteningContrastLevel:LighteningContrastLevel.lighteningContrastHigh,lighteningLevel: .5,smoothnessLevel: .5,rednessLevel: .5,sharpnessLevel: .5,),);
属性
|
作用
|
lighteningContrastLevel
|
对比度,常与 lighteningLevel 搭配使用。取值越大,明暗对比程度越大
|
lighteningLevel
|
美白程度,取值范围为 [0.0,1.0],其中 0.0 表示原始亮度,默认值为 0.0。取值越大,美白程度越大
|
smoothnessLevel
|
磨皮程度,取值范围为 [0.0,1.0],其中 0.0 表示原始磨皮程度,默认值为 0.0。取值越大,磨皮程度越大
|
rednessLevel
|
红润度,取值范围为 [0.0,1.0],其中 0.0 表示原始红润度,默认值为 0.0。取值越大,红润程度越大
|
sharpnessLevel
|
锐化程度,取值范围为 [0.0,1.0],其中 0.0 表示原始锐度,默认值为 0.0。取值越大,锐化程度越大
|
运行后效果如下图所示,开了 0.5 参数后的美颜整体画面更加白皙,同时唇色也更加明显。
| 没开美颜 | 开了美颜 |
![]() |
![]() |
接下来要介绍的一个 API 是色彩增强:setColorEnhanceOptions,如果是美颜还无法满足你的需求,那么色彩增强 API 可以提供更多参数来调整你的需要的画面风格。
如下代码所示,色彩增强 API 很简单,主要是调整ColorEnhanceOptions的 strengthLevel和skinProtectLevel参数,也就是调整色彩强度和肤色保护的效果。
_engine.setColorEnhanceOptions(enabled: true,options: const ColorEnhanceOptions(strengthLevel: 6.0, skinProtectLevel: 0.7));
开启增强之后画面更抢眼了。
| 没开增强 |
开了美颜+增强 |
![]() |
![]() |
属性
|
参数
|
strengthLevel
|
色彩增强程度。取值范围为 [0.0,1.0]。0.0 表示不对视频进行色彩增强。取值越大,色彩增强的程度越大。默认值为 0.5。
|
skinProtectLevel
|
肤色保护程度。取值范围为 [0.0,1.0]。0.0 表示不对肤色进行保护。取值越大,肤色保护的程度越大。默认值为 1.0。当色彩增强程度较大时,人像肤色会明显失真,你需要设置肤色保护程度;肤色保护程度较大时,色彩增强效果会略微降低。因此,为获取最佳的色彩增强效果,建议动态调节 strengthLevel 和 skinProtectLevel 以实现最合适的效果。
|
其实声音调教才是重头戏,声网既然叫声网,在音频处理上肯定不能落后,在声网 SDK 里就可以通过enableSpatialAudio打开空间音效的效果。
_engine.enableSpatialAudio(true);
本质上空间音效就是通过一些声学相关算法计算,模拟实现类似空间 3D 效果的音效实现。
同时你还可以通过setRemoteUserSpatialAudioParams来配置空间音效的相关参数,如下表格所示,可以看到声网提供了非常丰富的参数来让我们可以自主调整空间音效,例如这里面的enable_blur和enable_air_absorb效果就很有意思,十分推荐大家去试试。
属性
|
作用
|
speaker_azimuth
|
远端用户或媒体播放器相对于本地用户的水平角。取值范围为 [0,360],单位为度,例如 (默认)0 度,表示水平面的正前方;90 度,表示水平面的正左方;180 度,表示水平面的正后方;270 度,表示水平面的正右方;360 度,表示水平面的正前方;
|
speaker_elevation
|
远端用户或媒体播放器相对于本地用户的俯仰角。取值范围为 [-90,90],单位为度。(默认)0 度,表示水平面无旋转;-90 度,表示水平面向下旋转 90 度;90 度,表示水平面向上旋转 90 度
|
speaker_distance
|
远端用户或媒体播放器相对于本地用户的距离,取值范围为 [1,50],单位为米,默认值为 1 米。
|
speaker_orientation
|
远端用户或媒体播放器相对于本地用户的朝向。取值范围为 [0,180],单位为度。默认)0 度,表示声源和听者朝向同一方向;180: 180 度,表示声源和听者面对面
|
enable_blur
|
是否开启声音模糊处理
|
enable_air_absorb
|
是否开启空气衰减,即模拟声音在空气中传播的音色衰减效果:在一定的传输距离下,高频声音衰减速度快、低频声音衰减速度慢。
|
speaker_attenuation
|
远端用户或媒体播放器的声音衰减系数,取值范围为[0,1]。0:广播模式,即音量和音色均不随距离衰减;(0,0.5):弱衰减模式,即音量和音色在传播过程中仅发生微弱衰减;0.5:(默认)模拟音量在真实环境下的衰减,效果等同于不设置 speaker_attenuation 参数;(0.5,1]:强衰减模式,即音量和音色在传播过程中发生迅速衰减
|
enable_doppler
|
是否开启多普勒音效:当声源与接收声源者之间产生相对位移时,接收方听到的音调会发生变化
|
音频类的效果这里就无法展示了,强烈推荐大家自己动手去试试。
另外一个推荐的 API 就是人声音效:setAudioEffectPreset, 调用该方法可以通过 SDK 预设的人声音效,在不会改变原声的性别特征的前提下,修改用户的人声效果,例如:
_engine.setAudioEffectPreset(AudioEffectPreset.roomAcousticsKtv);
声网 SDK 里预设了非常丰富的AudioEffectPreset,如下表格所示,从场景效果如 KTV、录音棚,到男女变声,再到恶搞的音效猪八戒等,可以说是相当惊艳。
参数
|
作用
|
audioEffectOff
|
原声
|
roomAcousticsKtv
|
KTV
|
roomAcousticsVocalConcert
|
演唱会
|
roomAcousticsStudio
|
录音棚
|
roomAcousticsPhonograph
|
留声机
|
roomAcousticsVirtualStereo
|
虚拟立体声
|
roomAcousticsSpacial
|
空旷
|
roomAcousticsEthereal
|
空灵
|
roomAcousticsVirtualSurroundSound
|
虚拟环绕声
|
roomAcoustics3dVoice
|
3D 人声
|
voiceChangerEffectUncle
|
大叔
|
voiceChangerEffectOldman
|
老年男性
|
voiceChangerEffectBoy
|
男孩
|
voiceChangerEffectSister
|
少女
|
voiceChangerEffectGirl
|
女孩
|
voiceChangerEffectPigking
|
猪八戒
|
voiceChangerEffectHulk
|
绿巨人
|
styleTransformationRnb
|
R&B
|
styleTransformationPopular
|
流行
|
pitchCorrection
|
电音
|
_engine.setAudioProfile(profile: AudioProfileType.audioProfileDefault,scenario: AudioScenarioType.audioScenarioGameStreaming);
最后,完整代码如下所示:
class VideoChatPage extends StatefulWidget {const VideoChatPage({Key? key}) : super(key: key);State<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 = {};void 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();}void dispose() {_engine.leaveChannel();super.dispose();}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(() {});}}
本篇的内容作为上一篇的补充,相对来说内容还是比较简单,不过可以看到不管是在画面处理还是在声音处理上,声网 SDK 都提供了非常便捷的 API 实现,特别在声音处理上,因为文章限制这里只展示了简单的 API 介绍,所以强烈建议大家自己尝试下这些音频 API ,真的非常有趣。除此之外,还有许多场景与玩法,可以点击下方‘阅读原文’访问官网了解。





